diff --git a/README.md b/README.md index 54615b75..96df4148 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,12 @@ MARKDOWN=false # OIDC_TOKEN_URL=https://oauth2.googleapis.com/token # OIDC_ISSUER=https://accounts.google.com # OIDC_PROVIDER_NAME=Google + +# Set to false to disable pledge only users from being able to suggest items to another user's list +PLEDGE_SUGGEST=true + +# Set to false to disable all users from being able to suggest items to another users's list +SUGGESTIONS_ENABLED=true ``` ## Default Profile Pictures diff --git a/package.json b/package.json index 1b16dd4e..ee9e215c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node built/manager.js", - "dev": "nodemon --watch src -e ts,js --exec 'npm run build && node built/index.js'", + "dev": "nodemon --watch src -e ts,js,pug --exec 'npm run build && node built/index.js'", "postinstall": "node postinstall.cjs", "build": "tsc", "release": "docker buildx build . --platform linux/amd64,linux/arm64 -t wingysam/christmas-community -t wingysam/christmas-community:$(./node_modules/node-jq/bin/jq -r .version < package.json) --push" diff --git a/src/config/index.ts b/src/config/index.ts index 6c11e4c3..74b57ce3 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -30,7 +30,9 @@ const config = { oidcProviderName: process.env.OIDC_PROVIDER_NAME || 'Google', oidcEnabled: false, rootUrl: appendSlash(process.env.ROOT_URL ?? process.env.ROOT_PATH ?? '/'), - base: '' // automatically set below + base: '', // automatically set below + pledgeSuggest: (process.env.PLEDGE_SUGGEST === 'true'), + suggestionsEnabled: (process.env.SUGGESTIONS_ENABLED === 'true') } if (config.guestPassword) config.wishlist.public = false diff --git a/src/languages/en-us.ts b/src/languages/en-us.ts index 0a8b4bb0..39db6c0d 100644 --- a/src/languages/en-us.ts +++ b/src/languages/en-us.ts @@ -16,6 +16,7 @@ export const strings = { ADMIN_SETTINGS_USERS_ADD_HEADER: 'Add user', ADMIN_SETTINGS_USERS_ADD_PLACEHOLDER: 'john', ADMIN_SETTINGS_USERS_ADD_USERNAME: 'Username', + ADMIN_SETTINGS_USERS_ADD_PLEDGE_ONLY: 'Pledge only user?', ADMIN_SETTINGS_USERS_ADD_ERROR_USERNAME_EMPTY: 'Username cannot be empty.', ADMIN_SETTINGS_USERS_EDIT_DELETE_FAIL_ADMIN: 'Failed to remove: user is admin.', ADMIN_SETTINGS_USERS_EDIT_DELETE_SUCCESS: name => `Successfully removed user ${name}`, @@ -29,9 +30,16 @@ export const strings = { ADMIN_SETTINGS_USERS_EDIT_PROMOTE_SUCCESS: name => `${name} is now an admin.`, ADMIN_SETTINGS_USERS_EDIT_RENAMED_USER: 'Renamed user!', ADMIN_SETTINGS_USERS_EDIT_SAME_NAME: 'Username is same as new username.', + ADMIN_USER_EDIT_PLEDGE_ONLY: 'Pledge only', + ADMIN_USER_EDIT_PLEDGE_ONLY_BUTTON: 'Change pledge only', ADMIN_SETTINGS_USERS_EDIT: 'Edit', ADMIN_SETTINGS_USERS_HEADER: 'Users', ADMIN_SETTINGS_VERSION_INFO: 'Version Info', + ADMIN_SETTINGS_TABLE_USERNAME: 'Username', + ADMIN_SETTINGS_TABLE_ADMIN_USER: 'Admin', + ADMIN_SETTINGS_TABLE_PLEDGE_ONLY: 'Pledge only', + ADMIN_SETTINGS_TABLE_WISHLIST_COUNT: 'Wishlist items', + ADMIN_SETTINGS_TABLE_EDIT: 'Edit', ADMIN_USER_EDIT_ACCOUNT_UNCONFIRMED: "This account hasn't been confirmed.", ADMIN_USER_EDIT_ADMIN_ISADMIN: name => `${name} is an admin.`, ADMIN_USER_EDIT_ADMIN_NOTADMIN: name => `${name} is not an admin.`, @@ -208,7 +216,10 @@ export const strings = { WISHLIST_URL_LABEL: `Item URL or Name (Supported Sites)`, WISHLIST_URL_PLACEHOLDER: 'https://www.amazon.com/dp/B00ZV9RDKK', WISHLIST_URL_REQUIRED: 'Item URL or Name is required', + WISHLIST_SUGGESTIONS_DISABLED: 'Item suggestions are disabled.', WISHLISTS_COUNTS_SELF: name => `${name}: ???/???`, WISHLISTS_COUNTS: (name, pledged, total) => `${name}: ${pledged}/${total}`, - WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists` + WISHLISTS_TITLE: `${_CC.config.siteTitle} - Wishlists`, + YES: 'Yes', + NO: 'No' } as const diff --git a/src/routes/adminSettings/index.js b/src/routes/adminSettings/index.js index 4f492c07..bc70e717 100644 --- a/src/routes/adminSettings/index.js +++ b/src/routes/adminSettings/index.js @@ -44,10 +44,14 @@ export default function ({ db, ensurePfp }) { }) } + const pledgeOnlyValue = req.body.newPledgeOnlyUser + const pledgeOnly = pledgeOnlyValue === 'on' + await db.put({ _id: username, admin: false, wishlist: [], + pledgeOnly: pledgeOnly, signupToken: nanoid(SECRET_TOKEN_LENGTH), expiry: new Date().getTime() + SECRET_TOKEN_LIFETIME @@ -139,6 +143,23 @@ export default function ({ db, ensurePfp }) { } }) + router.post('/edit/pledgeOnly/:userToChange', verifyAuth(), async (req, res) => { + + const newPledgeValue = req.body.editPledgeOnlyUser + const newPledge = newPledgeValue === 'on' + + const userDoc = await db.get(req.params.userToChange) + userDoc.pledgeOnly = newPledge + try { + await db.put(userDoc) + await _CC.wishlistManager.clearCache() + return res.redirect(`/admin-settings/edit/${req.params.userToChange}`) + } catch (error) { + req.flash('error', error.message) + return res.redirect(`/admin-settings/edit/${req.params.userToChange}`) + } + }) + router.post('/edit/impersonate/:userToEdit', verifyAuth(), async (req, res) => { if (!req.user.admin) return res.redirect('/') req.login({ _id: req.params.userToEdit }, err => { diff --git a/src/routes/setup/index.js b/src/routes/setup/index.js index ad03e3a0..91b79609 100644 --- a/src/routes/setup/index.js +++ b/src/routes/setup/index.js @@ -28,7 +28,8 @@ export default function ({ db, ensurePfp }) { password: adminPasswordHash, admin: true, wishlist: [], - oauthConnections: {} + oauthConnections: {}, + pledgeOnly: false }) resolve() }) diff --git a/src/static/css/main.css b/src/static/css/main.css index 0b4dfdd9..a97e32f8 100644 --- a/src/static/css/main.css +++ b/src/static/css/main.css @@ -64,4 +64,9 @@ img.logo-image { .print-gift:last-child { margin-bottom: 0; +} + +a.disabled { + pointer-events: none; + cursor: default; } \ No newline at end of file diff --git a/src/views/admin-user-edit.pug b/src/views/admin-user-edit.pug index c7869334..0976aa00 100644 --- a/src/views/admin-user-edit.pug +++ b/src/views/admin-user-edit.pug @@ -44,6 +44,15 @@ block content .field .control input.button.is-primary(type='submit' value=lang('ADMIN_USER_EDIT_CHANGE_USERNAME')) + .column.is-narrow + h2= lang('ADMIN_USER_EDIT_PLEDGE_ONLY') + form(action=`${_CC.config.base}admin-settings/edit/pledgeOnly/${user._id}`, method='POST') + .field + .control + input(type='checkbox', name='editPledgeOnlyUser', checked=user.pledgeOnly) + .field + .control + input.input.button(type='submit', value=lang('ADMIN_USER_EDIT_PLEDGE_ONLY_BUTTON'), style='margin-top: 1em;') .column.is-narrow h2= lang('ADMIN_USER_EDIT_ADMIN') //- Yes, ternary exists, but I think the code is cleaner with a more "naive" style :) diff --git a/src/views/adminSettings.pug b/src/views/adminSettings.pug index b91b0086..be95f330 100644 --- a/src/views/adminSettings.pug +++ b/src/views/adminSettings.pug @@ -2,13 +2,26 @@ extends layout.pug block content h2= lang('ADMIN_SETTINGS_USERS_HEADER') - each user in users - span.is-size-6.inline= user.id - a(href=`${_CC.config.base}admin-settings/edit/${user.id}`) - span.is-size-7.icon.has-text-info - i.fas.fa-edit - span.is-sr-only - = lang('ADMIN_SETTINGS_USERS_EDIT') + table.table + thead + th=lang('ADMIN_SETTINGS_TABLE_USERNAME') + th=lang('ADMIN_SETTINGS_TABLE_ADMIN_USER') + th=lang('ADMIN_SETTINGS_TABLE_PLEDGE_ONLY') + th=lang('ADMIN_SETTINGS_TABLE_WISHLIST_COUNT') + th=lang('ADMIN_SETTINGS_TABLE_EDIT') + tbody + each user in users + tr(id=user.id) + td=user.id + td=user.doc.admin ? lang('YES') : lang('NO') + td=user.doc.pledgeOnly ? lang('YES') : lang('NO') + td=Object.keys(user.doc.wishlist).length + td + a(href=`${_CC.config.base}admin-settings/edit/${user.id}`) + span.is-size-7.icon.has-text-info + i.fas.fa-edit + span.is-sr-only + = lang('ADMIN_SETTINGS_USERS_EDIT') br h3= lang('ADMIN_SETTINGS_USERS_ADD_HEADER') form(action=`${_CC.config.base}admin-settings/add`, method='POST') @@ -19,6 +32,10 @@ block content input.input(type='text', name='newUserUsername', placeholder=lang('ADMIN_SETTINGS_USERS_ADD_PLACEHOLDER')) span.icon.is-small.is-left i.fas.fa-user + .field + label.label= lang('ADMIN_SETTINGS_USERS_ADD_PLEDGE_ONLY') + .control.has-icons-left + input(type='checkbox', name='newPledgeOnlyUser') .field .control input.button.is-primary(type='submit' value=lang('ADMIN_SETTINGS_USERS_ADD_BUTTON')) diff --git a/src/views/includes/navbar.pug b/src/views/includes/navbar.pug index 092c8285..1c42ac01 100644 --- a/src/views/includes/navbar.pug +++ b/src/views/includes/navbar.pug @@ -30,7 +30,8 @@ nav.navbar.is-fixed-top(role='navigation', aria-label='main navigation',style='b .navbar-item.has-dropdown.is-hoverable a.navbar-link= req.user._id .navbar-dropdown - +navBarLink(`${_CC.config.base}wishlist/${req.user._id}`, lang('NAVBAR_WISHLIST')) + if !req.user.pledgeOnly + +navBarLink(`${_CC.config.base}wishlist/${req.user._id}`, lang('NAVBAR_WISHLIST')) +navBarLink(`${_CC.config.base}profile`, lang('NAVBAR_PROFILE')) if req.user.admin +navBarLink(`${_CC.config.base}admin-settings`, lang('NAVBAR_ADMIN')) diff --git a/src/views/profile.pug b/src/views/profile.pug index 6abee5d3..cfd750d1 100644 --- a/src/views/profile.pug +++ b/src/views/profile.pug @@ -36,21 +36,22 @@ block content button.button.is-primary(type='submit') span.icon i.fas.fa-save - h2= lang('PROFILE_SHARED_INFORMATION') - form(action=`${_CC.config.base}profile/info`, method='POST') - .columns.is-multiline.is-mobile - .column.is-narrow - +sharedInfoProp('shoeSize', 'PROFILE_SHOE_SIZE') - +sharedInfoProp('ringSize', 'PROFILE_RING_SIZE') - +sharedInfoProp('dressSize', 'PROFILE_DRESS_SIZE') - .column.is-narrow - +sharedInfoProp('sweaterSize', 'PROFILE_SWEATER_SIZE') - +sharedInfoProp('shirtSize', 'PROFILE_SHIRT_SIZE') - +sharedInfoProp('pantsSize', 'PROFILE_PANTS_SIZE') - .column.is-narrow - +sharedInfoProp('coatSize', 'PROFILE_COAT_SIZE') - +sharedInfoProp('hatSize', 'PROFILE_HAT_SIZE') - +sharedInfoProp('phoneModel', 'PROFILE_PHONE_MODEL') + if !req.user.pledgeOnly + h2= lang('PROFILE_SHARED_INFORMATION') + form(action=`${_CC.config.base}profile/info`, method='POST') + .columns.is-multiline.is-mobile + .column.is-narrow + +sharedInfoProp('shoeSize', 'PROFILE_SHOE_SIZE') + +sharedInfoProp('ringSize', 'PROFILE_RING_SIZE') + +sharedInfoProp('dressSize', 'PROFILE_DRESS_SIZE') + .column.is-narrow + +sharedInfoProp('sweaterSize', 'PROFILE_SWEATER_SIZE') + +sharedInfoProp('shirtSize', 'PROFILE_SHIRT_SIZE') + +sharedInfoProp('pantsSize', 'PROFILE_PANTS_SIZE') + .column.is-narrow + +sharedInfoProp('coatSize', 'PROFILE_COAT_SIZE') + +sharedInfoProp('hatSize', 'PROFILE_HAT_SIZE') + +sharedInfoProp('phoneModel', 'PROFILE_PHONE_MODEL') h2= lang('PROFILE_SECURITY') a.button.is-primary(href=`${_CC.config.base}profile/password`) diff --git a/src/views/wishlist.pug b/src/views/wishlist.pug index 221b2e62..d2440076 100644 --- a/src/views/wishlist.pug +++ b/src/views/wishlist.pug @@ -18,6 +18,9 @@ block title span= lang('WISHLIST_TITLE', name) .level-right .level-item + if ((!global._CC.config.pledgeSuggest && req.user.pledgeOnly) || !global._CC.config.suggestionsEnabled) + span= " " + else a(href="#addWishlistItem").button.button.is-primary.is-rounded span.icon i.fas.fa-plus @@ -262,30 +265,34 @@ block content .field.inline .control.inline input.inline.button(type='submit' value=lang('WISHLIST_MOVE_ITEM_BOTTOM')) - form(method='POST')#addWishlistItem - .field - label.label!=lang('WISHLIST_URL_LABEL') - .control.has-icons-left - input.input( - type='text', - name='itemUrlOrName', - placeholder=lang('WISHLIST_URL_PLACEHOLDER') - ) - span.icon.is-small.is-left - i.fas.fa-gift - .field - label.label= lang('WISHLIST_NOTE') - .control - textarea.textarea( - name='note', - placeholder=lang('WISHLIST_OPTIONAL') - ) - .field.is-grouped - .control - input.button(type='submit' value=(req.user._id === req.params.user ? lang('WISHLIST_ADD') : lang('WISHLIST_PLEDGE_ITEM'))) - if req.user._id !== req.params.user + + if ((!global._CC.config.pledgeSuggest && req.user.pledgeOnly) || !global._CC.config.suggestionsEnabled) + .notification.is-info= lang('WISHLIST_SUGGESTIONS_DISABLED') + else + form(method='POST')#addWishlistItem + .field + label.label!=lang('WISHLIST_URL_LABEL') + .control.has-icons-left + input.input( + type='text', + name='itemUrlOrName', + placeholder=lang('WISHLIST_URL_PLACEHOLDER') + ) + span.icon.is-small.is-left + i.fas.fa-gift + .field + label.label= lang('WISHLIST_NOTE') + .control + textarea.textarea( + name='note', + placeholder=lang('WISHLIST_OPTIONAL') + ) + .field.is-grouped .control - input.inline.button(type='submit', value=lang('WISHLIST_SUGGEST'), name='suggest') + input.button(type='submit' value=(req.user._id === req.params.user ? lang('WISHLIST_ADD') : lang('WISHLIST_PLEDGE_ITEM'))) + if req.user._id !== req.params.user + .control + input.inline.button(type='submit', value=lang('WISHLIST_SUGGEST'), name='suggest') script(src=`${_CC.config.base}js/wishlist.js`) block print diff --git a/src/views/wishlists.pug b/src/views/wishlists.pug index 4243928a..66dae17c 100644 --- a/src/views/wishlists.pug +++ b/src/views/wishlists.pug @@ -52,7 +52,7 @@ block content div!= _CC.config.customHtml.wishlists ul.noStyle.noLeftMargin - if req.user._id !== '_CCUNKNOWN' + if req.user._id !== '_CCUNKNOWN' && !req.user.pledgeOnly li .box a(href=`${_CC.config.base}wishlist/${req.user._id}`, style='color: #4a4a4a;') @@ -67,7 +67,7 @@ block content progress.progress(value=1, max=1) +wishlistDetails(req.user.wishlist, req.user._id) each user in users - if req.user._id !== user.id + if req.user._id !== user.id && !user.doc.pledgeOnly li .box a(href=`${_CC.config.base}wishlist/${user.id}`, style='color: #4a4a4a;')