feat(org): Admin updates for curated sets
This commit is contained in:
parent
a0754bf218
commit
dba3c3731a
8 changed files with 176 additions and 12 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
|||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
dump
|
||||
production.sqlite
|
||||
|
||||
# .env
|
||||
.env
|
||||
|
|
46
sites/org/pages/admin/cset/[id].mjs
Normal file
46
sites/org/pages/admin/cset/[id].mjs
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { nsMerge } from 'shared/utils.mjs'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
// Hooks
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Components
|
||||
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||
import { AuthWrapper, ns as authNs } from 'shared/components/wrappers/auth/index.mjs'
|
||||
import { EditCuratedSet, ns as csetNs } from 'shared/components/curated-sets.mjs'
|
||||
|
||||
// Translation namespaces used on this page
|
||||
const ns = nsMerge(pageNs, authNs, csetNs, 'curate')
|
||||
|
||||
const EditCuratedSetPage = ({ page, id }) => {
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
return (
|
||||
<PageWrapper {...page} title={`${t('curate:set')}: ${id}`}>
|
||||
<AuthWrapper requiredRole="curator">
|
||||
<EditCuratedSet id={id} />
|
||||
</AuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditCuratedSetPage
|
||||
|
||||
export async function getStaticProps({ locale, params }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ns)),
|
||||
id: params.id,
|
||||
page: {
|
||||
locale,
|
||||
path: ['curate', 'sets', params.id],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
return {
|
||||
paths: [],
|
||||
fallback: true,
|
||||
}
|
||||
}
|
38
sites/org/pages/admin/cset/index.mjs
Normal file
38
sites/org/pages/admin/cset/index.mjs
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import { nsMerge } from 'shared/utils.mjs'
|
||||
// Hooks
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
// Components
|
||||
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||
import { AuthWrapper, ns as authNs } from 'shared/components/wrappers/auth/index.mjs'
|
||||
import { Loading } from 'shared/components/spinner.mjs'
|
||||
import { Hits } from 'shared/components/admin.mjs'
|
||||
import { CuratedSetsList } from 'shared/components/curated-sets.mjs'
|
||||
|
||||
// Translation namespaces used on this page
|
||||
const namespaces = nsMerge(pageNs, authNs)
|
||||
|
||||
const AdminPage = ({ page }) => (
|
||||
<PageWrapper {...page} title="Manage Curated Sets">
|
||||
<AuthWrapper requiredRole="admin">
|
||||
<CuratedSetsList href={(id) => `/admin/cset/${id}`} />
|
||||
</AuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
export default AdminPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['admin', 'cset'],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
|||
import { AuthWrapper, ns as authNs } from 'shared/components/wrappers/auth/index.mjs'
|
||||
import { Loading } from 'shared/components/spinner.mjs'
|
||||
import { Hits } from 'shared/components/admin.mjs'
|
||||
import { PageLink } from 'shared/components/link.mjs'
|
||||
|
||||
// Translation namespaces used on this page
|
||||
const namespaces = nsMerge(pageNs, authNs)
|
||||
|
@ -39,6 +40,10 @@ const AdminPage = ({ page }) => {
|
|||
return (
|
||||
<PageWrapper {...page} title="Administration">
|
||||
<AuthWrapper requiredRole="admin">
|
||||
<p>
|
||||
Other admin links:
|
||||
<PageLink href="/admin/cset" txt="Curated measurement sets" />
|
||||
</p>
|
||||
<h5>Search users</h5>
|
||||
<input
|
||||
autoFocus
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
} from 'shared/utils.mjs'
|
||||
import { measurements } from 'config/measurements.mjs'
|
||||
import { measurements as designMeasurements } from 'shared/prebuild/data/design-measurements.mjs'
|
||||
//import orderBy from 'lodash.orderby'
|
||||
import orderBy from 'lodash.orderby'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
|
@ -44,6 +44,7 @@ import {
|
|||
MeasieInput,
|
||||
DesignDropdown,
|
||||
ListInput,
|
||||
NumberInput,
|
||||
ns as inputNs,
|
||||
} from 'shared/components/inputs.mjs'
|
||||
|
||||
|
@ -65,7 +66,7 @@ const SetLineup = ({ sets = [], href = false, onClick = false }) => (
|
|||
const props = {
|
||||
className: 'aspect-[1/3] w-auto h-96',
|
||||
style: {
|
||||
backgroundImage: `url(${cloudflareImageUrl({ id: set.img, type: 'lineup' })})`,
|
||||
backgroundImage: `url(${cloudflareImageUrl({ id: `cset-${set.id}`, type: 'lineup' })})`,
|
||||
width: 'auto',
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
|
@ -118,7 +119,7 @@ const ShowCuratedSet = ({ cset }) => {
|
|||
onClick={() =>
|
||||
setModal(
|
||||
<ModalWrapper flex="col" justify="top lg:justify-center" slideFrom="right">
|
||||
<img src={cloudflareImageUrl({ type: 'lineup', id: cset.img })} />
|
||||
<img src={cloudflareImageUrl({ type: 'lineup', id: `cset-${cset.id}` })} />
|
||||
</ModalWrapper>
|
||||
)
|
||||
}
|
||||
|
@ -130,7 +131,10 @@ const ShowCuratedSet = ({ cset }) => {
|
|||
</div>
|
||||
|
||||
<h2>{t('data')}</h2>
|
||||
<DisplayRow title={t('name')}>{cset[`name${capitalize(lang)}`]}</DisplayRow>
|
||||
<DisplayRow title={t('name')}>
|
||||
<PageLink href={`/curated-sets/${cset.id}`} txt={cset[`name${capitalize(lang)}`]} />
|
||||
</DisplayRow>
|
||||
<DisplayRow title={t('height')}>{cset.height}cm</DisplayRow>
|
||||
{control >= controlLevels.sets.notes && (
|
||||
<DisplayRow title={t('notes')}>
|
||||
<Mdx md={cset[`notes${capitalize(lang)}`]} />
|
||||
|
@ -198,7 +202,7 @@ export const CuratedSet = ({ id }) => {
|
|||
export const CuratedSetPicker = (props) => <CuratedSets {...props} />
|
||||
|
||||
// Component for the curated-sets page
|
||||
export const CuratedSets = ({ href = false, clickHandler = false }) => {
|
||||
export const CuratedSets = ({ href = false, clickHandler = false, published = true }) => {
|
||||
// Hooks
|
||||
const backend = useBackend()
|
||||
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
||||
|
@ -214,7 +218,9 @@ export const CuratedSets = ({ href = false, clickHandler = false }) => {
|
|||
const result = await backend.getCuratedSets()
|
||||
if (result.success) {
|
||||
const allSets = {}
|
||||
for (const set of result.data.curatedSets) allSets[set.id] = set
|
||||
for (const set of result.data.curatedSets) {
|
||||
if (!published || set.published) allSets[set.id] = set
|
||||
}
|
||||
setSets(allSets)
|
||||
setLoadingStatus([true, 'status:dataLoaded', true, true])
|
||||
} else setLoadingStatus([true, 'status:backendError', true, false])
|
||||
|
@ -223,7 +229,7 @@ export const CuratedSets = ({ href = false, clickHandler = false }) => {
|
|||
}, [])
|
||||
|
||||
const lineupProps = {
|
||||
sets: Object.values(sets),
|
||||
sets: orderBy(sets, 'height', 'asc'),
|
||||
}
|
||||
if (typeof href === 'function') lineupProps.href = href
|
||||
else lineupProps.onClick = clickHandler ? clickHandler : (set) => setSelected(set.id)
|
||||
|
@ -236,7 +242,7 @@ export const CuratedSets = ({ href = false, clickHandler = false }) => {
|
|||
)
|
||||
}
|
||||
|
||||
// Component for the maintaining the list of curated-sets
|
||||
// Component for the maintaining the list of curated-sets
|
||||
export const CuratedSetsList = ({ href = false }) => {
|
||||
// Hooks
|
||||
const { t } = useTranslation(ns)
|
||||
|
@ -328,6 +334,7 @@ export const CuratedSetsList = ({ href = false }) => {
|
|||
<th className="text-base-300 text-base">{t('curate:img')}</th>
|
||||
<th className="text-base-300 text-base">{t('curate:name')}</th>
|
||||
<th className="text-base-300 text-base">{t('curate:published')}</th>
|
||||
<th className="text-base-300 text-base">{t('curate:height')}</th>
|
||||
<th className="text-base-300 text-base">{t('curate:createdAt')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -342,15 +349,23 @@ export const CuratedSetsList = ({ href = false }) => {
|
|||
onClick={() => toggleSelect(set.id)}
|
||||
/>
|
||||
</td>
|
||||
<td>{set.id}</td>
|
||||
<td>
|
||||
<PageLink href={typeof href === 'function' ? href(set.id) : href} txt={set.id} />
|
||||
</td>
|
||||
<td>
|
||||
<img
|
||||
src={cloudflareImageUrl({ id: set.img, variant: 'sq100' })}
|
||||
src={cloudflareImageUrl({ id: `cset-${set.id}`, variant: 'sq100' })}
|
||||
className="mask mask-squircle w-12 h-12"
|
||||
/>
|
||||
</td>
|
||||
<td>{set.nameEn}</td>
|
||||
<td>
|
||||
<PageLink
|
||||
href={typeof href === 'function' ? href(set.id) : href}
|
||||
txt={set.nameEn}
|
||||
/>
|
||||
</td>
|
||||
<td>{set.published ? <BoolYesIcon /> : <BoolNoIcon />}</td>
|
||||
<td>{set.height}cm</td>
|
||||
<td>{set.createdAt}</td>
|
||||
</tr>
|
||||
))}
|
||||
|
@ -415,6 +430,7 @@ export const EditCuratedSet = ({ id }) => {
|
|||
k = `notes${capitalize(lang)}`
|
||||
if (data[k] !== cset[k]) changes[k] = data[k]
|
||||
}
|
||||
if (data.height !== cset.height) changes.height = Number(data.height)
|
||||
if (data.img !== cset.img) changes.img = data.img
|
||||
if (data.published !== cset.published) changes.published = data.published
|
||||
for (const m in data.measies) {
|
||||
|
@ -431,7 +447,7 @@ export const EditCuratedSet = ({ id }) => {
|
|||
<div className="max-w-2xl">
|
||||
<PageLink href={`/curated-sets/${id}`} txt={`/curated-sets/${id}`} />
|
||||
<ListInput
|
||||
label={t('curate:publshed')}
|
||||
label={t('curate:published')}
|
||||
update={(val) => updateData('published', val)}
|
||||
list={[
|
||||
{
|
||||
|
@ -458,6 +474,17 @@ export const EditCuratedSet = ({ id }) => {
|
|||
current={data.published}
|
||||
/>
|
||||
|
||||
<NumberInput
|
||||
min={42}
|
||||
max={215}
|
||||
step={1}
|
||||
key="height"
|
||||
label="Height"
|
||||
update={(val) => updateData('height', val)}
|
||||
current={Number(data.height)}
|
||||
valid={notEmpty}
|
||||
/>
|
||||
|
||||
<h2 id="measies">{t('measies')}</h2>
|
||||
<div className="bg-secondary px-4 pt-1 pb-4 rounded-lg shadow bg-opacity-10">
|
||||
<DesignDropdown
|
||||
|
|
|
@ -130,6 +130,39 @@ export const ButtonFrame = ({
|
|||
</button>
|
||||
)
|
||||
|
||||
/*
|
||||
* Input for integers
|
||||
*/
|
||||
export const NumberInput = ({
|
||||
label, // Label to use
|
||||
update, // onChange handler
|
||||
valid, // Method that should return whether the value is valid or not
|
||||
current, // The current value
|
||||
original, // The original value
|
||||
placeholder, // The placeholder text
|
||||
docs = false, // Docs to load, if any
|
||||
id = '', // An id to tie the input to the label
|
||||
labelBL = false, // Bottom-Left label
|
||||
labelBR = false, // Bottom-Right label
|
||||
max = 0,
|
||||
min = 220,
|
||||
step = 1,
|
||||
}) => (
|
||||
<FormControl {...{ label, labelBL, labelBR, docs }} forId={id}>
|
||||
<input
|
||||
id={id}
|
||||
type="number"
|
||||
placeholder={placeholder}
|
||||
value={current}
|
||||
onChange={(evt) => update(evt.target.value)}
|
||||
className={`input w-full input-bordered ${
|
||||
current === original ? 'input-secondary' : valid(current) ? 'input-success' : 'input-error'
|
||||
}`}
|
||||
{...{ max, min, step }}
|
||||
/>
|
||||
</FormControl>
|
||||
)
|
||||
|
||||
/*
|
||||
* Input for strings
|
||||
*/
|
||||
|
|
|
@ -28,6 +28,7 @@ sets: Your Measurements Sets
|
|||
patterns: Your Patterns
|
||||
curate: Curate
|
||||
curateSets: Curate Sets
|
||||
curatedSets: Curated Measurements Sets
|
||||
code: Code
|
||||
patternsAbout: Lists the patterns that you have stored in your FreeSewing account
|
||||
setsAbout: Lists the measurements sets that you have stored in your FreeSewing account
|
||||
|
|
|
@ -112,6 +112,11 @@ export const extendSiteNav = async (siteNav, lang) => {
|
|||
t: t('sections:admin'),
|
||||
_: 1,
|
||||
s: 'admin',
|
||||
cset: {
|
||||
t: 'Curated Measurement Sets',
|
||||
s: 'admin/cset',
|
||||
_: 1,
|
||||
},
|
||||
}
|
||||
|
||||
// Add account
|
||||
|
@ -161,6 +166,14 @@ export const extendSiteNav = async (siteNav, lang) => {
|
|||
t: t('yourProfile'),
|
||||
}
|
||||
|
||||
// Add curated measurements sets
|
||||
siteNav['curated-sets'] = {
|
||||
m: 1,
|
||||
s: 'curated-sets',
|
||||
t: t('sections:curatedSets'),
|
||||
n: 1,
|
||||
}
|
||||
|
||||
// Add translation
|
||||
siteNav.translation = {
|
||||
s: 'translation',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue