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
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
@@ -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
@@ -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
Expand Up @@ -5,7 +5,7 @@ import { GameBadge } from '../../../game-lib'
import styles from './AwardedMembersTab.module.scss'

export interface AwardedMembersTabProps {
awardedMembers?: GameBadge['member_badges']
badge: GameBadge
}

const AwardedMembersTab: FC<AwardedMembersTabProps> = (props: AwardedMembersTabProps) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,11 @@ const BadgeDetailPage: FC = () => {

// default tab
let activeTabElement: JSX.Element
= <AwardedMembersTab
awardedMembers={badgeDetailsHandler.data?.member_badges}
/>
= <AwardedMembersTab badge={badgeDetailsHandler.data as GameBadge} />
if (activeTab === BadgeDetailsTabViews.manualAward) {
activeTabElement = <ManualAwardTab awardedMembers={badgeDetailsHandler.data?.member_badges} />
activeTabElement = <ManualAwardTab
badge={badgeDetailsHandler.data as GameBadge}
/>
}
if (activeTab === BadgeDetailsTabViews.batchAward) {
activeTabElement = <BatchAwardTab />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,46 @@
import { find } from 'lodash'
import { Dispatch, FC, SetStateAction, useState } from 'react'

import { Button } from '../../../../../lib'
import { InputHandleAutocomplete } from '../../../../../lib/member-autocomplete'
import { MembersAutocompeteResult } from '../../../../../lib/member-autocomplete/input-handle-functions'
import { GameBadge } from '../../../game-lib'
import { BadgeAssignedModal } from '../../../game-lib/modals/badge-assigned-modal'
import { generateCSV, manualAssignRequestAsync } from '../badge-details.functions'

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

export interface ManualAwardTabProps {
awardedMembers?: GameBadge['member_badges']
badge: GameBadge
}

const ManualAwardTab: FC<ManualAwardTabProps> = (props: ManualAwardTabProps) => {

const [selectedMembers, setSelectedMembers]: [Array<MembersAutocompeteResult>, Dispatch<SetStateAction<Array<MembersAutocompeteResult>>>]
= useState<Array<MembersAutocompeteResult>>([])

const [showBadgeAssigned, setShowBadgeAssigned]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false)

const [badgeAssignError, setBadgeAssignError]: [string | undefined, Dispatch<SetStateAction<string | undefined>>] = useState<string | undefined>()

function onAward(): void {
setSelectedMembers([])
const csv: string = generateCSV(
selectedMembers.map(m => [m.handle, props.badge?.id as string])
)
setBadgeAssignError(undefined)
manualAssignRequestAsync(csv)
.then(() => {
setShowBadgeAssigned(true)
setSelectedMembers([])
})
.catch(e => {
let message: string = e.message
if (e.errors && e.errors[0] && e.errors[0].path === 'user_id') {
const handleOrId: string = find(selectedMembers, { userId: e.errors[0].value })?.handle || e.errors[0].value
message = `Member ${handleOrId} alredy owns this badge.`
}
setBadgeAssignError(message)
})
}

return (
Expand All @@ -33,6 +56,8 @@ const ManualAwardTab: FC<ManualAwardTabProps> = (props: ManualAwardTabProps) =>
onChange={setSelectedMembers}
tabIndex={0}
value={selectedMembers}
error={badgeAssignError}
dirty={!!badgeAssignError}
/>
<div className={styles.actionsWrap}>
<Button
Expand All @@ -45,6 +70,15 @@ const ManualAwardTab: FC<ManualAwardTabProps> = (props: ManualAwardTabProps) =>
</div>
</div>
</div>
{
showBadgeAssigned && <BadgeAssignedModal
badge={props.badge}
isOpen={showBadgeAssigned}
onClose={() => {
setShowBadgeAssigned(false)
}}
/>
}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { GamificationConfig } from '../../game-config'
import { GameBadge } from '../../game-lib'

import { submitRequestAsync as submitBadgeAssingRequestAsync } from './manual-assign-badge.store'
import { submitRequestAsync as submitBadgeUpdateRequestAsync } from './update-badge.store'
import { UpdateBadgeRequest } from './updated-badge-request.model'

export async function submitRequestAsync(request: UpdateBadgeRequest): Promise<GameBadge> {
return submitBadgeUpdateRequestAsync(request)
}

export function generateCSV(input: Array<Array<string | number>>): string {
input.unshift(GamificationConfig.CSV_HEADER)

return input.map(row => row.join(',')).join('\n')
}

export async function manualAssignRequestAsync(csv: string): Promise<any> {
return submitBadgeAssingRequestAsync(csv)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { EnvironmentConfig } from '../../../../config'
import { xhrPostAsync } from '../../../../lib'

export async function submitRequestAsync(csv: string): Promise<any> {
const url: string = `${EnvironmentConfig.API.V5}/gamification/badges/assign`

const form: any = new FormData()

// fill the form
form.append('file', new Blob([csv], { type: 'text/csv' }), 'data.csv')

return xhrPostAsync(url, form)
}