Admin can manage users
parent
f10d245b20
commit
26addb2e40
@ -0,0 +1,101 @@
|
||||
import router from 'next/router'
|
||||
import {DateTime} from 'luxon'
|
||||
|
||||
import AdminToolbar from '~/components/admin/actionBar'
|
||||
import Table from '~/components/table'
|
||||
|
||||
UserDetails.getInitialProps = async ({ctx: {axios, query: {uuid}}}) => {
|
||||
const {data: user} = await axios.get(`/api/users/${uuid}`)
|
||||
const {data: orders} = await axios.get(`/api/users/${uuid}/orders`)
|
||||
return {user, orders: orders.sort(sortOrders)}
|
||||
}
|
||||
|
||||
export default function UserDetails({user, orders}) {
|
||||
return (
|
||||
<>
|
||||
<AdminToolbar title={`User Details: ${user.email}`}/>
|
||||
<h3>User details</h3>
|
||||
<div style={{maxWidth: 700, margin: '0 auto'}}>
|
||||
<p><strong>Registered:</strong> {DateTime.fromISO(user.time_registered).toLocal().toFormat('LLLL dd, h:mm a')}</p>
|
||||
<p><strong>Email:</strong> {user.email}</p>
|
||||
<p><strong>Password:</strong> {!user.password_hash ? 'Unset' : <>Set.</>}</p>
|
||||
</div>
|
||||
|
||||
<h3>Their Orders</h3>
|
||||
<Table
|
||||
columns={[
|
||||
{name: 'Purchased', extractor: getPurchaseTime},
|
||||
{name: 'Items', extractor: getNumberItems},
|
||||
{name: 'Item Price', extractor: getItemPrice},
|
||||
{name: 'Shipping', extractor: getShippingEstimate},
|
||||
{name: 'Total', extractor: getAmountPaid},
|
||||
{name: '', extractor: order =>
|
||||
<button className="buttonLink" onClick={() => router.push(`/admin/orders/${order.uuid}`)}>Details</button>
|
||||
}
|
||||
]}
|
||||
rows={orders}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getPurchaseTime(order){
|
||||
const mostRecentTransaction = order.transactions.sort(sortTransactions)[0]
|
||||
const time = parsePaymentTime(mostRecentTransaction)
|
||||
|
||||
return time.setZone('local').toFormat('LLLL dd, h:mm a')
|
||||
}
|
||||
|
||||
function getNumberItems(order){
|
||||
return order.transactions.map(transaction =>
|
||||
transaction.cart.items.map(item => item.count)
|
||||
).reduce((a,b)=>(a+b))
|
||||
}
|
||||
|
||||
function getItemPrice(order){
|
||||
return formatMoney(order.transactions.map(transaction => transaction.item_total_price)
|
||||
.reduce((a,b)=>(a+b)))
|
||||
}
|
||||
|
||||
function getShippingEstimate(order){
|
||||
return formatMoney(order.transactions.map(transaction => transaction.shipping_price)
|
||||
.reduce((a,b)=>(a+b)))
|
||||
}
|
||||
|
||||
function getAmountPaid(order){
|
||||
return formatMoney(order.transactions.map(({payments}) => payments.map(payment => payment.value_cents))
|
||||
.flat()
|
||||
.reduce((a,b)=>(a+b)))
|
||||
}
|
||||
|
||||
function parsePaymentTime({payments}){
|
||||
for(const payment of payments) {
|
||||
if(typeof payment.time === 'string')
|
||||
payment.time = DateTime.fromISO(payment.time)
|
||||
}
|
||||
|
||||
payments.sort((a,b) => b.time.diff(a.time))
|
||||
return payments[0].time
|
||||
}
|
||||
|
||||
function sortTransactions(a,b){
|
||||
const timeA = parsePaymentTime(a)
|
||||
const timeB = parsePaymentTime(b)
|
||||
|
||||
return timeB.diff(timeA).as('seconds')
|
||||
}
|
||||
|
||||
function sortOrders(a,b){
|
||||
const timePaidA = parsePaymentTime(a.transactions.sort(sortTransactions)[0])
|
||||
const timePaidB = parsePaymentTime(b.transactions.sort(sortTransactions)[0])
|
||||
|
||||
return timePaidB.diff(timePaidA).as('seconds')
|
||||
}
|
||||
|
||||
const formatMoney = money => {
|
||||
if (money === undefined || money === null) return null;
|
||||
|
||||
return '$' + (money / 100).toFixed(2)
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import React, {useState} from 'react'
|
||||
import router from 'next/router'
|
||||
import Link from 'next/link'
|
||||
import {DateTime} from 'luxon'
|
||||
import {Icon} from '@rmwc/icon'
|
||||
import axios from 'axios'
|
||||
|
||||
import useUser, {useSetUser} from '~/hooks/useUser'
|
||||
import AdminToolbar from '~/components/admin/actionBar'
|
||||
import Table from '~/components/table'
|
||||
|
||||
UsersTable.getInitialProps = async ({ctx: {axios}}) => {
|
||||
const {data: users} = await axios.get(`/api/users`)
|
||||
return {users}
|
||||
}
|
||||
|
||||
export default function UsersTable({users: _users}) {
|
||||
const currentUser = useUser()
|
||||
const setCurrentUser = useSetUser()
|
||||
const [users, setUsers] = useState(_users)
|
||||
|
||||
const promoteAdmin = uuid => async ev => {
|
||||
if(ev) ev.preventDefault()
|
||||
if(!window.confirm(`Are you sure you want to make this user an admin?`))
|
||||
return
|
||||
|
||||
const {data: user} = await axios.put(`/api/users/${uuid}/admin`)
|
||||
|
||||
if(user.uuid === currentUser.uuid)
|
||||
setCurrentUser(user)
|
||||
|
||||
setUsers(users.map(u => u.uuid === uuid ? user : u))
|
||||
}
|
||||
|
||||
const demoteAdmin = uuid => async ev => {
|
||||
if(ev) ev.preventDefault()
|
||||
if(!window.confirm(`Are you sure you want to demote this user?`))
|
||||
return
|
||||
|
||||
const {data: user} = await axios.delete(`/api/users/${uuid}/admin`)
|
||||
|
||||
if(user.uuid === currentUser.uuid){
|
||||
setCurrentUser(user)
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
setUsers(users.map(u => u.uuid === uuid ? user : u))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AdminToolbar title="Users"/>
|
||||
<Table
|
||||
columns={[
|
||||
{name: 'Email', extractor: user => user.email},
|
||||
{name: 'Orders', extractor: user => user.num_orders},
|
||||
{name: 'Registered', extractor: user => DateTime.fromISO(user.time_registered).setZone('local').toFormat('LLL dd')},
|
||||
{name: 'Email Confirmed', extractor: user => user.email_confirmed ? <Icon icon="check"/> : <Icon icon="cancel"/>},
|
||||
{name: 'Admin', extractor: user => user.is_admin ? <Icon icon="check"/> : <Icon icon="cancel"/>},
|
||||
{name: 'Actions', extractor: user => (
|
||||
<span>
|
||||
<Link href={`/admin/users/${user.uuid}`}><a>Details</a></Link>
|
||||
{user.is_admin
|
||||
? <button onClick={demoteAdmin(user.uuid)} type="button" className="buttonLink">Remove Admin</button>
|
||||
: <button onClick={promoteAdmin(user.uuid)} type="button" className="buttonLink">Make Admin</button>
|
||||
}
|
||||
|
||||
</span>
|
||||
)}
|
||||
]}
|
||||
rows={users}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue