Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,17 @@
"highlight.js": "^11.6.0",
"html2canvas": "^1.4.1",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"marked": "4.0.3",
"moment": "^2.29.3",
"moment-timezone": "^0.5.34",
"prop-types": "^15.8.1",
"qs": "^6.11.0",
"rc-checkbox": "^2.3.2",
"react": "^17.0.2",
"react-apexcharts": "^1.4.0",
"react-app-rewired": "^2.2.1",
"react-contenteditable": "^3.3.6",
"react-dom": "^17.0.2",
"react-elastic-carousel": "^0.11.5",
"react-gtm-module": "^2.0.11",
Expand Down Expand Up @@ -85,6 +88,7 @@
"@types/highlightjs": "^9.12.2",
"@types/jest": "^27.0.1",
"@types/lodash": "^4.14.182",
"@types/markdown-it": "^12.2.3",
"@types/marked": "4.0.3",
"@types/node": "^18.7.13",
"@types/reach__router": "^1.3.10",
Expand Down
2 changes: 2 additions & 0 deletions src-ts/lib/functions/xhr-functions/xhr.functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ function interceptError(instance: AxiosInstance): void {

// if there is server error message, then return it inside `message` property of error
error.message = error?.response?.data?.message || error.message
// if there is server errors data, then return it inside `errors` property of error
error.errors = error?.response?.data?.errors

return Promise.reject(error)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import "../styles/variables/palette";

.memberSelect {
color: $black-60;
}
94 changes: 94 additions & 0 deletions src-ts/lib/member-autocomplete/InputHandleAutocomplete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { FC, FocusEvent } from 'react'
import { MultiValue, StylesConfig } from 'react-select'
// tslint:disable-next-line: no-submodule-imports
import AsyncSelect from 'react-select/async'

import { InputWrapper } from '../form/form-groups/form-input/input-wrapper'

import { membersAutocompete, MembersAutocompeteResult } from './input-handle-functions'
import styles from './InputHandleAutocomplete.module.scss'

export interface InputHandleAutocompleteProps {
readonly className?: string
readonly dirty?: boolean
readonly disabled?: boolean
readonly error?: string
readonly hideInlineErrors?: boolean
readonly hint?: string
readonly label?: string | JSX.Element
readonly name: string
readonly onBlur?: (event: FocusEvent<HTMLInputElement>) => void
readonly onChange: (newValue: Array<MembersAutocompeteResult>) => void
readonly placeholder?: string
readonly tabIndex: number
readonly value?: Array<MembersAutocompeteResult>
}

const InputHandleAutocomplete: FC<InputHandleAutocompleteProps> = (props: InputHandleAutocompleteProps) => {
const customStyles: StylesConfig<any> = {
control: (provided) => ({
...provided,
border: 'none',
}),
input: (provided) => ({
...provided,
color: 'inherit',
fontSize: 16,
}),
multiValue: (provided) => ({
...provided,
borderRadius: 50,
}),
multiValueLabel: (provided) => ({
...provided,
fontSize: 12,
}),
option: (provided) => ({
...provided,
borderBottom: '1px solid #E9E9E9',
color: 'inherit',
fontSize: 16,
fontWeight: 400,
padding: 16,
}),
placeholder: (provided) => ({
...provided,
color: 'inherit',
fontSize: 16,
fontWeight: 400,
}),
valueContainer: (provided) => ({
...provided,
padding: 0,
}),
}

return (
<InputWrapper
{...props}
dirty={!!props.dirty}
disabled={!!props.disabled}
label={props.label || props.name}
hideInlineErrors={props.hideInlineErrors}
type='text'
>
<AsyncSelect
className={styles.memberSelect}
cacheOptions
getOptionLabel={({ handle }) => handle}
getOptionValue={({ userId }) => userId}
isMulti
key={props.value?.length}
loadOptions={membersAutocompete}
styles={customStyles}
placeholder={props.placeholder}
onBlur={props.onBlur}
onChange={(newValue: MultiValue<MembersAutocompeteResult>) => props.onChange(newValue as Array<MembersAutocompeteResult>)}
value={props.value}
isDisabled={props.disabled}
/>
</InputWrapper>
)
}

export default InputHandleAutocomplete
1 change: 1 addition & 0 deletions src-ts/lib/member-autocomplete/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as InputHandleAutocomplete } from './InputHandleAutocomplete'
23 changes: 23 additions & 0 deletions src-ts/lib/member-autocomplete/input-handle-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import qs from 'qs'

import { xhrGetAsync } from '..'
import { EnvironmentConfig } from '../../config'

export interface MembersAutocompeteQuery {
term: string
}

export interface MembersAutocompeteResult {
firstName: string
handle: string
lastName: string
userId: string
}

export async function membersAutocompete(term: string): Promise<Array<MembersAutocompeteResult>> {
const query: MembersAutocompeteQuery = {
term,
}

return xhrGetAsync(`${EnvironmentConfig.API.V5}/members/autocomplete?${qs.stringify(query)}`)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface GamificationConfigModel {
ACCEPTED_BADGE_MIME_TYPES: string
CSV_HEADER: Array<string>,
MAX_BADGE_IMAGE_FILE_SIZE: number
ORG_ID: string
PAGE_SIZE: number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GamificationConfigModel } from './gamification-config.model'

export const GamificationConfigDefault: GamificationConfigModel = {
ACCEPTED_BADGE_MIME_TYPES: 'image/svg+xml,image/svg',
CSV_HEADER: ['tc_handle', 'badge_id'],
MAX_BADGE_IMAGE_FILE_SIZE: 5000000, // 5mb in bytes
ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3',
PAGE_SIZE: 12,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ export interface GameBadge {
badge_name: string
badge_status: string
id: string
member_badges?: Array<{
awarded_at: string,
awarded_by: string,
user_handle: string,
user_id: string,
}>
organization_id: string
}
1 change: 1 addition & 0 deletions src-ts/tools/gamification-admin/game-lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './game-badge.model'
export * from './use-get-game-badges-page.hook'
export * from './use-gamification-breadcrumb.hook'
export * from './use-get-game-badge-details.hook'
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
@import "../../../../../lib/styles/variables";
@import "../../../../../lib/styles/includes";

.wrapper {
display: flex;
flex-direction: column;
justify-content: space-between;

.badge {
display: flex;
align-items: center;
margin-bottom: $space-xxl;

@include ltemd {
margin-bottom: 0;
}

.badge-image {
width: 43px;
height: 43px;
margin-right: $space-xl;
}

.badge-image-disabled {
width: 43px;
height: 43px;
margin-right: $space-xl;
opacity: 0.5;
filter: grayscale(1);
}

.badge-name {
font-size: 16px;
}
}

.actions-wrap {
display: flex;
flex-direction: column;

.actions {
display: flex;
align-items: center;

@include ltemd {
justify-content: flex-end;
}

a {
margin-right: $space-md;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { FC } from 'react'

import { BaseModal, Button, PageDivider, useCheckIsMobile } from '../../../../../lib'
import { GameBadge } from '../../game-badge.model'

import styles from './BadgeAssignedModal.module.scss'
export interface BadgeAssignedModalProps {
badge: GameBadge
isOpen: boolean
onClose: () => void
}

const BadgeAssignedModal: FC<BadgeAssignedModalProps> = (props: BadgeAssignedModalProps) => {

const isMobile: boolean = useCheckIsMobile()

function onClose(): void {
props.onClose()
}

return (
<BaseModal
onClose={onClose}
open={props.isOpen}
size='md'
title={`Badge created`}
closeOnOverlayClick={false}
>
<div className={styles.wrapper}>
<div className={styles.badge}>
<img
alt={props.badge.badge_name}
className={styles[props.badge.active ? 'badge-image' : 'badge-image-disabled']}
src={props.badge.badge_image_url}
/>
<p className={styles['badge-name']}>{props.badge.badge_name} badge has been sucessfully awarded.</p>
</div>
<div className={styles['actions-wrap']}>
{
isMobile && <PageDivider />
}
<div className={styles.actions}>
<Button
label='Close'
buttonStyle='primary'
onClick={onClose}
/>
</div>
</div>
</div>
</BaseModal>
)
}

export default BadgeAssignedModal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as BadgeAssignedModal } from './BadgeAssignedModal'
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import useSWR, { KeyedMutator, SWRResponse } from 'swr'

import { EnvironmentConfig } from '../../../config'

import { GameBadge } from './game-badge.model'

export interface BadgeDetailPageHandler<T> {
data?: Readonly<T>
error?: Readonly<any>
mutate: KeyedMutator<any>
}

export function useGetGameBadgeDetails(badgeID: string): BadgeDetailPageHandler<GameBadge> {

const badgeEndpointUrl: URL = new URL(`${EnvironmentConfig.API.V5}/gamification/badges/${badgeID}`)

const { data, error, mutate }: SWRResponse = useSWR(badgeEndpointUrl.toString())

return {
data,
error,
mutate,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.tabWrap {
display: flex;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { FC } from 'react'

import { GameBadge } from '../../../game-lib'

import styles from './AwardedMembersTab.module.scss'

export interface AwardedMembersTabProps {
badge: GameBadge
}

const AwardedMembersTab: FC<AwardedMembersTabProps> = (props: AwardedMembersTabProps) => {
return (
<div className={styles.tabWrap}>

</div>
)
}

export default AwardedMembersTab
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './AwardedMembersTab'
Loading