-
-
+ )
+ }
+
+
+ {
+ Boolean(!upImg && !result.success) && (
+
+ )
+ }
+ {
+ Boolean(upImg && !result.success) && (
+
-
-
+ )
+ }
+
+
+ {
+ !result.success && (
+
+
+ {
+ upImg && (
+
+ )
+ }
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+ )
+ }
)
}
diff --git a/src/components/UploadAvatarModal/UploadAvatarModal.module.scss b/src/components/UploadAvatarModal/UploadAvatarModal.module.scss
index 3ac4897b5..a3c660bb3 100644
--- a/src/components/UploadAvatarModal/UploadAvatarModal.module.scss
+++ b/src/components/UploadAvatarModal/UploadAvatarModal.module.scss
@@ -1,18 +1,31 @@
-.zoomAndCrop {
+// Specificity assurance
+:global(.message).message {
+ margin: 2rem auto;
+}
+
+.avatarPreview {
display: flex;
+ justify-content: center;
+
+ width: 100%;
+ margin: 2rem 0 0;
+}
+
+.body {
+ .zoomAndCrop {
+ display: flex;
+
+ margin: 0 3.4rem 0 0;
+ }
+
.zoomSliderBox {
display: flex;
flex-direction: column;
- width: 30px;
- margin-bottom: 30px;
-
- .zoomSliderIcon {
- height: 30px;
- padding: 5px none;
- }
+ width: 3.4rem;
+ margin-bottom: 3.2rem;
}
.sliderControl {
@@ -29,14 +42,7 @@
.rotateSliderBox {
display: flex;
- .sliderControl {
- padding-top: 5px;
- }
-
- .rotateSliderIcon {
- width: 30px;
- padding: none 5px;
- }
+ padding-top: 1.8rem;
}
.rotateAndCrop {
diff --git a/src/components/UserDecalPanel/UserDecalPanel.module.scss b/src/components/UserDecalPanel/UserDecalPanel.module.scss
index ff850eba9..b8f132e3f 100644
--- a/src/components/UserDecalPanel/UserDecalPanel.module.scss
+++ b/src/components/UserDecalPanel/UserDecalPanel.module.scss
@@ -1,3 +1,4 @@
+@import '../../scss/fonts';
@import '../../scss/colors';
.panelContent {
@@ -29,7 +30,7 @@
.decalCode {
flex: 1 0 auto;
- font-family: monospace;
+ font-family: $monospace-fonts;
}
.decalClaimedAt {
diff --git a/src/components/UserMenu/UserMenu.js b/src/components/UserMenu/UserMenu.js
index fa7bbcf63..356afc1de 100644
--- a/src/components/UserMenu/UserMenu.js
+++ b/src/components/UserMenu/UserMenu.js
@@ -8,7 +8,7 @@ import { logout } from '~/store/actions/session'
import {
selectSession,
selectUserById,
- selectAvatarByUserId,
+ selectAvatarUrlByUserId,
withCurrentUserId,
selectCurrentUserCanEditAllRescues,
} from '~/store/selectors'
@@ -26,7 +26,7 @@ function UserMenu () {
const { loggedIn } = useSelector(selectSession)
const userCanSeeRescueAdmin = useSelector(selectCurrentUserCanEditAllRescues)
const user = useSelector(withCurrentUserId(selectUserById))
- const userAvatar = useSelectorWithProps({ size: 64 }, withCurrentUserId(selectAvatarByUserId))
+ const userAvatarUrl = useSelectorWithProps({ size: 64 }, withCurrentUserId(selectAvatarUrlByUserId))
const dispatch = useDispatch()
@@ -62,7 +62,7 @@ function UserMenu () {
unoptimized
alt="User's avatar"
height={64}
- src={userAvatar}
+ src={userAvatarUrl}
width={64} />
)
}
diff --git a/src/scss/_components.scss b/src/scss/_components.scss
index 8f9a396e3..e41205b92 100644
--- a/src/scss/_components.scss
+++ b/src/scss/_components.scss
@@ -12,6 +12,7 @@
@import 'components/comments';
@import 'components/donate-form';
@import 'components/error';
+@import 'components/file-dropzone';
@import 'components/grid';
@import 'components/header';
@import 'components/hero';
diff --git a/src/scss/_lib.scss b/src/scss/_lib.scss
index 1a568489c..0914d5824 100644
--- a/src/scss/_lib.scss
+++ b/src/scss/_lib.scss
@@ -1,13 +1,13 @@
-/****************************************************************************** \
+/****************************************************************************** \
FontAwesome
-\ ******************************************************************************/
+\ ******************************************************************************/
@import '@fortawesome/fontawesome-svg-core/styles.css';
-/****************************************************************************** \
+/****************************************************************************** \
kbd fun
\ ******************************************************************************/
@import 'lib/kbdfun';
@@ -16,9 +16,9 @@
-/****************************************************************************** \
+/****************************************************************************** \
NProgress
-\ ******************************************************************************/
+\ ******************************************************************************/
@import 'nprogress/nprogress.css';
@import 'lib/nprogress';
@@ -26,9 +26,9 @@
-/****************************************************************************** \
+/****************************************************************************** \
React Table
-\ ******************************************************************************/
+\ ******************************************************************************/
@import 'react-table-6/react-table.css';
@import 'lib/react-table';
@@ -36,16 +36,26 @@
-/****************************************************************************** \
+/****************************************************************************** \
Stripe
-\ ******************************************************************************/
+\ ******************************************************************************/
@import 'lib/stripe';
-/****************************************************************************** \
+/****************************************************************************** \
RC-Slider
-\ ******************************************************************************/
+\ ******************************************************************************/
@import 'rc-slider/assets/index.css';
+@import 'lib/rc-slider';
+
+
+
+
+
+/****************************************************************************** \
+ react-easy-crop
+\ ******************************************************************************/
+@import 'lib/react-easy-crop';
diff --git a/src/scss/components/_avatar.scss b/src/scss/components/_avatar.scss
index 917d06320..1dd72bbe5 100644
--- a/src/scss/components/_avatar.scss
+++ b/src/scss/components/_avatar.scss
@@ -9,7 +9,7 @@
border: 0.2rem solid $red;
border-radius: 50%;
- background-color: $white;
+ background-color: $red-darker;
overflow: hidden;
@@ -32,17 +32,4 @@
width: 17rem;
height: 17rem;
}
-
- &:empty::before {
- align-items: center;
- justify-content: center;
-
- content: attr(data-letter);
- width: 100%;
-
- background: $white;
- color: $red;
-
- font-size: 2em;
- }
}
diff --git a/src/scss/components/_file-dropzone.scss b/src/scss/components/_file-dropzone.scss
new file mode 100644
index 000000000..0da2ced3e
--- /dev/null
+++ b/src/scss/components/_file-dropzone.scss
@@ -0,0 +1,51 @@
+label.file-dropzone {
+ display: block;
+ position: relative;
+
+ width: 100%;
+ padding: 4rem;
+
+ border: 2px dashed rgba($red-darkened, 0.4);
+
+ color: $red;
+ background: rgba($red, 0.2);
+ transition:
+ border-color 0.1s ease,
+ background 0.1s ease,
+ font-size 0.15s ease,
+ color 0.1s ease;
+
+ font-size: 1.5em;
+ font-weight: 600;
+ font-family: $title-fonts;
+ text-align: center;
+
+ cursor: pointer;
+
+ &.active {
+ border-color: rgba($green-darkened, 0.4);
+
+ color: $green;
+ background: rgba($green, 0.2);
+
+
+ font-size: 1.75em;
+ }
+
+ small {
+ font-size: 0.7em;
+ }
+
+ input {
+ position: absolute;
+ top: 0;
+ left: 0;
+
+ width: 100%;
+ height: 100%;
+
+ opacity: 0;
+
+ cursor: pointer;
+ }
+}
diff --git a/src/scss/lib/_rc-slider.scss b/src/scss/lib/_rc-slider.scss
new file mode 100644
index 000000000..3ae4ca3c8
--- /dev/null
+++ b/src/scss/lib/_rc-slider.scss
@@ -0,0 +1,86 @@
+.rc-slider {
+ &.rc-slider-disabled {
+ background-color: transparent;
+
+ .rc-slider-track {
+ background: rgba($grey-darkened, 0.2);
+ }
+
+ .rc-slider-handle {
+ background: $grey-muted;
+ }
+
+ .rc-slider-dot {
+ border-color: $grey-muted;
+ }
+ }
+
+ &.rc-slider-vertical {
+ .rc-slider-handle {
+ margin-left: -0.8rem;
+ }
+
+ .rc-slider-track,
+ .rc-slider-rail {
+ width: 0.6rem;
+ height: 100%;
+
+ border-radius: 1rem;
+ }
+
+ .rc-slider-dot {
+ left: 3px;
+ }
+ }
+
+ .rc-slider-rail {
+ height: 0.6rem;
+
+ border-radius: 1rem;
+
+ background: rgba($grey-darkened, 0.25);
+ }
+
+ .rc-slider-track {
+ height: 0.6rem;
+
+ border-radius: 1rem;
+
+ background: rgba($red-darkened, 0.2);
+ }
+
+ .rc-slider-dot {
+ bottom: -3px;
+
+ border-color: $red-darker;
+
+ background: transparent;
+ }
+
+ .rc-slider-handle {
+ width: 2.2rem;
+ height: 2.2rem;
+ margin-top: -0.8rem;
+
+ border: none;
+
+ background: $red;
+
+ .rc-slider-handle-icon {
+ position: absolute;
+ top: 0;
+ left: 0;
+
+ width: 2.2rem;
+ height: 2.2rem;
+ padding: 0.5rem;
+
+ color: $black;
+ }
+ }
+
+ .rc-slider-mark-text,
+ .rc-slider-mark-text.rc-slider-mark-text-active {
+ color: $grey;
+ }
+}
diff --git a/src/scss/lib/_react-easy-crop.scss b/src/scss/lib/_react-easy-crop.scss
new file mode 100644
index 000000000..ad7b2521b
--- /dev/null
+++ b/src/scss/lib/_react-easy-crop.scss
@@ -0,0 +1,3 @@
+.reactEasyCrop_Container.disabled {
+ cursor: not-allowed;
+}
diff --git a/src/store/selectors/clients.js b/src/store/selectors/clients.js
index 90f1ffb47..54dcf9df6 100644
--- a/src/store/selectors/clients.js
+++ b/src/store/selectors/clients.js
@@ -1,6 +1,6 @@
import { createCachedSelector } from 're-reselect'
-import { getUserId } from './users'
+import { getUserIdProp } from './users'
const selectClients = (state) => {
@@ -8,13 +8,13 @@ const selectClients = (state) => {
}
const selectClientsByUserId = createCachedSelector(
- [selectClients, getUserId],
+ [selectClients, getUserIdProp],
(clients, userId) => {
return Object.values(clients).filter((client) => {
return client.relationships.user?.data?.id === userId
}) ?? []
},
-)(getUserId)
+)(getUserIdProp)
export {
selectClients,
diff --git a/src/store/selectors/decals.js b/src/store/selectors/decals.js
index 855924116..d0239d350 100644
--- a/src/store/selectors/decals.js
+++ b/src/store/selectors/decals.js
@@ -4,7 +4,7 @@ import safeParseInt from '~/util/safeParseInt'
import { withCurrentUserId } from './session'
-import { getUserId, selectUserById } from './users'
+import { getUserIdProp, selectUserById } from './users'
@@ -39,4 +39,4 @@ export const selectDecalsByUserId = createCachedSelector(
}
return undefined
},
-)(getUserId)
+)(getUserIdProp)
diff --git a/src/store/selectors/groups.js b/src/store/selectors/groups.js
index a51e15490..042b1aaf6 100644
--- a/src/store/selectors/groups.js
+++ b/src/store/selectors/groups.js
@@ -1,6 +1,6 @@
import { createCachedSelector } from 're-reselect'
-import { getUserId, selectUserById } from './users'
+import { getUserIdProp, selectUserById } from './users'
@@ -26,4 +26,4 @@ export const selectGroupsByUserId = createCachedSelector(
}
return []
},
-)(getUserId)
+)(getUserIdProp)
diff --git a/src/store/selectors/nicknames.js b/src/store/selectors/nicknames.js
index 21df0f057..0e637e259 100644
--- a/src/store/selectors/nicknames.js
+++ b/src/store/selectors/nicknames.js
@@ -1,6 +1,6 @@
import { createCachedSelector } from 're-reselect'
-import { getUserId, selectUserById } from './users'
+import { getUserIdProp, selectUserById } from './users'
@@ -26,4 +26,4 @@ export const selectNicknamesByUserId = createCachedSelector(
}
return []
},
-)(getUserId)
+)(getUserIdProp)
diff --git a/src/store/selectors/statistics.js b/src/store/selectors/statistics.js
index 12705512b..ddc965a13 100644
--- a/src/store/selectors/statistics.js
+++ b/src/store/selectors/statistics.js
@@ -1,6 +1,6 @@
import { createCachedSelector } from 're-reselect'
-import { selectUserRatsRelationship, getUserId } from './users'
+import { selectUserRatsRelationship, getUserIdProp } from './users'
export const selectLeaderboard = (state) => {
return state.leaderboard.entries
@@ -21,7 +21,7 @@ export const selectRatStatisticsById = (state, { ratId }) => {
export const selectUserStatisticsById = createCachedSelector(
- [getUserId, selectUserRatsRelationship, selectRatStatistics],
+ [getUserIdProp, selectUserRatsRelationship, selectRatStatistics],
(userId, userRats, ratStatistics) => {
// if the user doesn't exist, or there's no rat data, or there's no statistics for the first rat, return null.
// Since the only way of getting a rat's statistics is by requesting for all rats of a user, we can assume we don't have any if we're missing one.
@@ -54,4 +54,4 @@ export const selectUserStatisticsById = createCachedSelector(
}, {}),
}
},
-)(getUserId)
+)(getUserIdProp)
diff --git a/src/store/selectors/users.js b/src/store/selectors/users.js
index d4445f6a8..fe78f91b8 100644
--- a/src/store/selectors/users.js
+++ b/src/store/selectors/users.js
@@ -15,11 +15,15 @@ const AVATAR_DEFAULT_SIZE = 256
-const getScope = (_, props) => {
+const getScopeProp = (_, props) => {
return props?.scope
}
-export const getUserId = (_, props) => {
+const getSizeProp = (_, props) => {
+ return props?.size ?? AVATAR_DEFAULT_SIZE
+}
+
+export const getUserIdProp = (_, props) => {
return props?.userId
}
@@ -40,16 +44,23 @@ export const selectUserDisplayRatRelationship = (state, props) => {
}
export const selectAvatarByUserId = (state, props) => {
- const user = selectUserById(state, props)
+ return selectUserById(state, props)?.relationships.avatar?.data ?? undefined
+}
- if (!user) {
- return undefined
- }
+export const selectAvatarUrlByUserId = createCachedSelector(
+ [selectAvatarByUserId, getSizeProp, getUserIdProp],
+ (avatar, size, userId) => {
+ if (userId === 'null') {
+ return undefined
+ }
- return user.attributes.image
- ? `/api/fr/users/${user.id}/image`
- : `/api/avatars/${user.id}/${props.size ?? AVATAR_DEFAULT_SIZE}`
-}
+ return avatar
+ ? `/api/fr/users/${userId}/image?v=${avatar.id}&size=${size}`
+ : `/api/avatars/${userId}/${size}`
+ },
+)((_, props) => {
+ return `${getUserIdProp(_, props)}-${getSizeProp(_, props)}`
+})
export const selectCurrentUserScopes = (state) => {
return withCurrentUserId(selectUserById)(state)?.meta?.permissions ?? []
@@ -66,7 +77,7 @@ export const selectCurrentUserScopes = (state) => {
export const selectCurrentUserHasScope = createCachedSelector(
[
selectCurrentUserScopes,
- getScope,
+ getScopeProp,
],
(userScopes, scope) => {
if (!scope) {
diff --git a/src/util/fontawesome/library.js b/src/util/fontawesome/library.js
index 21ac33c64..6b53efe61 100644
--- a/src/util/fontawesome/library.js
+++ b/src/util/fontawesome/library.js
@@ -19,20 +19,18 @@ export {
faExclamationCircle,
faEye,
faEyeSlash,
+ faFileUpload,
faFirstAid,
faFolder,
faFolderOpen,
faHandsHelping,
faIdCardAlt,
- faInfoCircle,
- faMoneyBill,
faMouse,
faPen,
faPlus,
faRocket,
faShieldAlt,
faShoePrints,
- faShoppingCart,
faSignature,
faSpaceShuttle,
faSpinner,
@@ -40,14 +38,10 @@ export {
faTimes,
faTimesCircle,
faTrash,
- faTruck,
- faUndo,
+ faSearch,
faUser,
faUserSecret,
faUpload,
- faSearchPlus,
- faSearchMinus,
- faRedo,
} from '@fortawesome/free-solid-svg-icons'
export {