Skip to content

Commit

Permalink
download certificate button added & working
Browse files Browse the repository at this point in the history
  • Loading branch information
divitcr7 committed Aug 8, 2024
1 parent 06cdc10 commit 9c4fbfc
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 87 deletions.
24 changes: 12 additions & 12 deletions backend/app/controllers/api/v1/badge_certificate_controller.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# frozen_string_literal: true

class Api::V1::BadgeCertificateController < Api::V1::BaseController
def show
def show
badge_id = params[:badge_id]
email = params[:email]

if badge_id.blank? || email.blank?
render json: { error: 'Badge ID and email are required' }, status: :unprocessable_entity
return
render json: { error: 'Badge ID and email are required' }, status: :unprocessable_entity
return
end

response = OpenBadgeApi.instance.get_pdf(badge_id, email)

if response[:pdf].present?
pdf_content = response[:pdf].read
base64_pdf = Base64.encode64(pdf_content)
render json: { pdf: base64_pdf }, status: :ok

pdf_content = response[:pdf].read
base64_pdf = Base64.encode64(pdf_content)
render json: { pdf: base64_pdf }, status: :ok
else
render json: { error: 'Failed to retrieve PDF' }, status: :bad_request
render json: { error: 'Failed to retrieve PDF' }, status: :bad_request
end
end
end
end


69 changes: 34 additions & 35 deletions backend/app/controllers/api/v1/badge_certificate_open_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,46 @@

class Api::V1::BadgeCertificateOpenApi
include OpenStax::OpenApi::Blocks

openapi_component do
schema :BadgeCertificateResponse do
property :pdf do
key :type, :string
key :description, 'Base64-encoded PDF data'
end

openapi_component do
schema :BadgeCertificateResponse do
property :pdf do
key :type, :string
key :description, 'Base64-encoded PDF data'
end
end

openapi_path '/badge_certificate' do
operation :get do
key :summary, 'Retrieve a PDF link for a badge'
key :description, 'Fetches a PDF link associated with a badge for a given email'
key :operationId, 'getBadgeCertificate'
end

parameter name: :badge_id, in: :query, required: true do
schema do
key :type, :string
key :description, 'Badge ID'
end
end

parameter name: :email, in: :query, required: true do
schema do
key :type, :string
key :description, 'Recipient Email'
end
openapi_path '/badge_certificate' do
operation :get do
key :summary, 'Retrieve a PDF link for a badge'
key :description, 'Fetches a PDF link associated with a badge for a given email'
key :operationId, 'getBadgeCertificate'

parameter name: :badge_id, in: :query, required: true do
schema do
key :type, :string
key :description, 'Badge ID'
end

response 200 do
key :description, 'PDF link retrieved successfully.'
content 'application/json' do
schema { key :$ref, :BadgeCertificateResponse }
end
end

parameter name: :email, in: :query, required: true do
schema do
key :type, :string
key :description, 'Recipient Email'
end
response 404 do
key :description, 'Badge or PDF link not found.'
end

response 200 do
key :description, 'PDF link retrieved successfully.'
content 'application/json' do
schema { key :$ref, :BadgeCertificateResponse }
end
extend Api::V1::OpenApiResponses::ServerError
end
response 404 do
key :description, 'Badge or PDF link not found.'
end
extend Api::V1::OpenApiResponses::ServerError
end
end
end
23 changes: 11 additions & 12 deletions backend/app/services/open_badge_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,13 @@ def issue_badge(badge_id, emails)
end

def get_pdf(badge_id, email)

# Response is an octet-stream
# Fetching the event id
event_response = HTTPX.plugin(:auth)
.with(headers: { 'content-type' => 'application/json' })
.authorization("Bearer #{token}")
.get("https://openbadgefactory.com/v1/event/#{@client_id}?email=#{email}")
.with(headers: { 'content-type' => 'application/json' })
.authorization("Bearer #{token}")
.get("https://openbadgefactory.com/v1/event/#{@client_id}?email=#{email}")

response_body = event_response.body.to_s
json_objects = response_body.split("\n").map(&:strip).reject(&:empty?)

Expand All @@ -78,19 +77,19 @@ def get_pdf(badge_id, email)

# Fetching the pdf link
pdf_response = HTTPX.plugin(:auth)
.with(headers: { 'content-type' => 'application/json' })
.authorization("Bearer #{token}")
.get("https://openbadgefactory.com/v1/event/#{@client_id}/#{event_id}/assertion")
.with(headers: { 'content-type' => 'application/json' })
.authorization("Bearer #{token}")
.get("https://openbadgefactory.com/v1/event/#{@client_id}/#{event_id}/assertion")

data = JSON.parse(pdf_response)
pdf_link = data['pdf']['en']

# Fetching the pdf from pdf_link
pdf_response = HTTPX.plugin(:auth)
.authorization("Bearer #{token}")
.get(pdf_link)
.authorization("Bearer #{token}")
.get(pdf_link)

{pdf: pdf_response.body}
{ pdf: pdf_response.body }
end

end
1 change: 0 additions & 1 deletion frontend/src/components/navbar/top-navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ export const TopNavBar: React.FC<TopNavBarProps> = ({ className }) => {
</Flex>
</NavbarStyledLink>
)}
// achievements page
{!isMobile &&
!user.isAdministrator &&
!user.isResearcher && (
Expand Down
72 changes: 45 additions & 27 deletions frontend/src/screens/achievements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import {
RingProgress,
Image,
} from '@mantine/core';
import { useApi } from '@lib'
import { useApi } from '@lib';
import { TopNavBar, Footer } from '@components';
import { colors } from '@theme';
import { StudyDetailsPreview } from '../screens/learner/details';
import { useParticipantStudies } from './learner/studies';
import { useCurrentUser } from '@lib';

const BadgeDetail = ({
badge,
Expand Down Expand Up @@ -95,8 +96,8 @@ const convertBase64ToPdf = (base64PDF: string) => {
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
return url
}
return url;
};

const AchievementBadge = ({
study,
Expand All @@ -107,6 +108,7 @@ const AchievementBadge = ({
onBadgeClick: (study: any) => void;
onStudySelect: (study: any) => void;
}) => {
const [errorMessage, setErrorMessage] = useState('');
const completedStudies = study?.learningPath?.studies.filter(
(s: any) => s.completedCount !== 0
).length;
Expand All @@ -119,20 +121,35 @@ const AchievementBadge = ({
? 'Continue'
: 'Start';

// event block statement to download pdf
const api = useApi()
const api = useApi();
const currentUser = useCurrentUser();

const handleButtonClick = async (
e: React.MouseEvent<HTMLButtonElement>
e: React.MouseEvent<HTMLButtonElement>,
badgeId: string
) => {
e.stopPropagation();
if (isCompleted) {
try{
const response = await api.getBadgeCertificate({badgeId: 'SAJSINa7DGDaC4D', email: '[email protected]'})
console.log(response.pdf)
const pdfUrl = convertBase64ToPdf(response.pdf || '')
try {
let userEmail = '';

if (currentUser && currentUser.contactInfos) {
const emailInfo = currentUser.contactInfos.find(
(info) => info.type === 'EmailAddress'
);
if (emailInfo && emailInfo.value) {
userEmail = emailInfo.value;
}
}

const response = await api.getBadgeCertificate({
badgeId,
email: userEmail,
});
const pdfUrl = convertBase64ToPdf(response.pdf || '');
window.open(pdfUrl, '_blank');
} catch (error) {
console.error('Error fetching PDF:', error);
setErrorMessage('Error fetching PDF. Please try again later.');
}
} else {
const nextStudy = study?.learningPath?.studies.find(
Expand Down Expand Up @@ -160,6 +177,12 @@ const AchievementBadge = ({
}}
onClick={() => onBadgeClick(study)}
>
{errorMessage && (
<Box style={{ color: 'red', marginBottom: '10px' }}>
{errorMessage}
</Box>
)}

<Box
style={{
width: '100%',
Expand All @@ -186,12 +209,13 @@ const AchievementBadge = ({
width: '250px',
height: '250px',
clipPath: 'polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%)',
background: study.learningPath?.badge.image ? 'none' : colors.white,
background: study.learningPath?.badge.image
? 'none'
: colors.white,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}

>
<Image
src={study.learningPath?.badge.image}
Expand Down Expand Up @@ -241,7 +265,7 @@ const AchievementBadge = ({
</Text>
<Text
size="xs"
color="dimmed"
color="dimmed"
mb={10}
style={{
fontFamily: 'System-ui',
Expand All @@ -254,7 +278,7 @@ const AchievementBadge = ({
{`${completedStudies} of ${study?.learningPath?.studies.length}`}
</Text>
<Button
onClick={handleButtonClick}
onClick={(e) => handleButtonClick(e, study.learningPath.badgeId)}
style={{
width: '200px',
height: '30px',
Expand Down Expand Up @@ -304,15 +328,16 @@ const TabButton = ({
</Button>
);


const Achievements = () => {
const [selectedTab, setSelectedTab] = useState<'Badges' | 'Points'>(
'Badges'
);
const [selectedStudy, setSelectedStudy] = useState(null);
const [badgeDetail, setBadgeDetail] = useState(null);

const DATA = useParticipantStudies();
const DATA = useParticipantStudies();
const studies = DATA.studies;

const handleTabClick = (tab: any) => setSelectedTab(tab);
const handleBadgeClick = (study: any) => {
setBadgeDetail(study);
Expand All @@ -339,16 +364,9 @@ const Achievements = () => {
<SimpleGrid
cols={{ base: 1, sm: 2, md: 3 }}
spacing={{ base: 40, sm: 60, md: 110 }}
style={{
marginTop: '100px',
padding: {
base: '330px',
sm: '40px',
md: '50px',
},
}}
style={{ marginTop: '100px' }}
>
{DATA.studies.map((study) => (
{studies.map((study) => (
<AchievementBadge
key={study.id}
study={study}
Expand Down Expand Up @@ -384,7 +402,7 @@ const Achievements = () => {
onClick={() => handleTabClick('Badges')}
/>
</Group>
{renderContent()} {/* Always render content without loading check */}
{renderContent()} {/* Always render content without loading check */}
</Container>
<Footer />
{selectedStudy && (
Expand Down

0 comments on commit 9c4fbfc

Please sign in to comment.