Skip to content
This repository has been archived by the owner on Jan 14, 2020. It is now read-only.

Commit

Permalink
Basic badge claiming by code.
Browse files Browse the repository at this point in the history
  • Loading branch information
brianloveswords committed Nov 6, 2012
1 parent f547bda commit 09a6ed7
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 7 deletions.
14 changes: 13 additions & 1 deletion app.js
Expand Up @@ -40,7 +40,8 @@ app.configure(function () {
'/login',
'/logout',
'/badge/*', // public badge resources
'/v1/*' // api endpoints
'/v1/*', // api endpoints
'/claim*'
],
redirectTo: '/login'
}));
Expand Down Expand Up @@ -96,6 +97,7 @@ app.post('/admin/badge/:shortname/behavior', badge.addBehavior);
app.delete('/admin/badge/:shortname/behavior', badge.removeBehavior);
app.post('/admin/badge/:shortname/claims', badge.addClaimCodes);
app.delete('/admin/badge/:shortname/claims/:code', badge.removeClaimCode);
app.patch('/admin/badge/:shortname/claims/:code', badge.releaseClaimCode);

// Creating new behaviors
// ----------------------
Expand All @@ -118,6 +120,16 @@ app.get('/badge/criteria/:shortname', [
findBadgeByParamShortname
], admin.criteria);

app.get('/claim', admin.claim);

app.post('/claim',[
badge.findByClaimCode()
], admin.confirmClaim);

app.post('/claim/confirm',[
badge.findByClaimCode()
], badge.awardToUser);

// User login/logout
// -------------------
app.get('/login', admin.login);
Expand Down
15 changes: 14 additions & 1 deletion models/badge.js
Expand Up @@ -341,7 +341,7 @@ Badge.prototype.redeemClaimCode = function redeemClaimCode(code, email) {
const claim = this.getClaimCode(code);
if (!claim)
return null;
if (claim.claimedBy)
if (claim.claimedBy && claim.claimedBy != email)
return false;
claim.claimedBy = email;
return true;
Expand All @@ -359,6 +359,19 @@ Badge.prototype.removeClaimCode = function removeClaimCode(code) {
});
};

/**
* Release a claim code back into the wild (remove `claimedBy`)
*
* @param {String} code
*/

Badge.prototype.releaseClaimCode = function releaseClaimCode(code) {
const claim = this.getClaimCode(code);
claim.claimedBy = null;
return true;
};


/**
* Check if the credits are enough to earn the badge
*
Expand Down
Binary file added public/img/bg-stone.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions public/js/claim-award.js
@@ -0,0 +1,33 @@
(function claimAward($) {
var $form = $('.js-confirm-form');
var $emailInput = $form.find('.js-email-input');
var where = $form.prop('action');
var method = $form.prop('method');
var code = $form.find('input[name="code"]').val();
var csrfToken = $form.find('input[name="csrf"]').val();

jQuery.ajaxSetup({
headers: {'x-csrf-token': csrfToken}
});

function getAssertion(email, callback) {
var data = { email: email, code: code};
jQuery.post('/claim/confirm', data)
.success(function (data) { callback(null, data) })
.error(function (err) { callback(err.statusText) });
}

$form.on('submit', function (e) {
var $this = $(this);
var email = $emailInput.val().trim();
var url;
getAssertion(email, function (err, data) {
if (err)
return window.alert('there was an error trying to claim the badge :(');
url = data.assertionUrl;
OpenBadges.issue([url]);
});

return (e.preventDefault(), false);
});
})(jQuery);
58 changes: 58 additions & 0 deletions public/stylesheets/criteria.css
Expand Up @@ -57,4 +57,62 @@ h2 {
line-height: 38px;
}

h3 {
margin: 0;
padding: 0;
}

.claim-form {
text-align: center;
padding: 30px 0;
}

.claim-form .input,
.confirm-form .input {
background: none repeat scroll 0 0 #FAFAFA;
border-color: #DBDBDB;
border-radius: 6px 6px 6px 6px;
border-style: solid;
border-width: 1px;
box-shadow: 0 2px 1px rgba(0, 0, 0, 0.1) inset;
font-size: inherit;
height: 1.714em;
line-height: 1.714em;
max-width: 450px;
padding: 4px 8px;
transition: box-shadow 0.1s linear 0s;
width: 100%;
}

.claim-form .button,
.confirm-form .button {
background-color: #276195;
background-image: linear-gradient(#3C88CC, #276195);
background-repeat: repeat-x;
border: 0 none;
border-radius: 0.25em 0.25em 0.25em 0.25em;
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.1), 0 -2px 0 0 rgba(0, 0, 0, 0.2) inset;
color: #FFFFFF;
display: inline-block;
font-family: arial,sans-serif;
font-size: 1em;
height: 2.5em;
line-height: 2.5em;
margin: 0;
padding: 0 1.5em;
text-align: center;
text-decoration: none;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
transition: box-shadow 0.25s linear 0s;
white-space: nowrap;
}

.confirm-form .input {
width: 400px;
}

.claim-form strong {
color: #e30;
}

/* END TEMPORARY CRITERIA STYLES */
2 changes: 1 addition & 1 deletion public/stylesheets/webmaker.css
Expand Up @@ -735,7 +735,7 @@ body {
border-top: none;
}
.bg-stone {
background: #f9f9f9 url(/media/img/bg-stone.png) 0 70px repeat-x;
background: #f9f9f9 url(/img/bg-stone.png) 0 70px repeat-x;
}
header {
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
Expand Down
16 changes: 16 additions & 0 deletions routes/admin.js
Expand Up @@ -76,6 +76,22 @@ exports.criteria = function criteria(req, res) {
});
}

exports.claim = function claim(req, res) {
return res.render('public/claim.html', {
csrf: req.session._csrf,
code: req.query.code,
missing: req.query.missing,
});
};

exports.confirmClaim = function confirmClaim(req, res) {
return res.render('public/confirm-claim.html', {
csrf: req.session._csrf,
code: req.body.code,
badge: req.badge,
});
};

exports.manageClaimCodes = function (req, res) {
return res.render('admin/manage-claim-codes.html', {
page: 'edit-badge',
Expand Down
46 changes: 44 additions & 2 deletions routes/badge.js
Expand Up @@ -131,11 +131,53 @@ exports.removeClaimCode = function removeClaimCode(req, res, next) {
var badge = req.badge;
badge.removeClaimCode(code);
badge.save(function (err) {
if (err) next(err);
res.redirect('back');
if (err) return next(err);
return res.redirect('back');
});
};

exports.releaseClaimCode = function releaseClaimCode(req, res, next) {
var code = req.param('code');
var badge = req.badge;
badge.releaseClaimCode(code);
badge.save(function (err) {
if (err) return next(err);
return res.redirect('back');
})
};


exports.awardToUser = function awardToUser(req, res, next) {
var email = req.body.email;
var code = req.body.code;
var badge = req.badge;
if (!badge.redeemClaimCode(code, email))
return res.send({ status: 'already-claimed' })
badge.awardOrFind(email, function (err, instance) {
if (err) return res.send({ status: 'error', error: err });
badge.save(function (err) {
if (err) return res.send({ status: 'error', error: err });
return res.send({
status: 'ok',
assertionUrl: instance.absoluteUrl('assertion')
});
})
});
};

exports.findByClaimCode = function findByClaimCode(options) {
return function (req, res, next) {
var code = req.body.code;
Badge.findByClaimCode(code, function (err, badge) {
if (err) return next(err);
if (!badge)
return res.redirect('/claim?code=' + code + '&missing=true');
req.badge = badge;
return next();
});
}
};

exports.findByShortName = function (options) {
var required = !!options.required;

Expand Down
29 changes: 29 additions & 0 deletions views/public/claim.html
@@ -0,0 +1,29 @@
{% extends "public/layout.html" %}
{% block head %}
<link rel="stylesheet" href="/stylesheets/criteria.css">
{% endblock %}
{% block body %}
<div class="row">
<div class="span7">
<h2>Mozilla Webmaker Badges</h2>
</div>
</div>
<div class="media card">
<div class="bd">
<hgroup>
<h3>What's your claim code?</h3>
</hgroup>

<form method="post" class="claim-form" action="">
{% if missing %}
<p><strong class="error">Sorry, could not find a badge with that code.</strong></p>
{% endif %}

<input id="name" type="hidden" name="csrf" value="{{ csrf }}">
<input tabindex="1" type="text" name="code" placeholder="Enter your claim code" class="input" value="{{ code | default('') }}">
<input tabindex="2" type="submit" value="Get your badge!" class="button">
</form>

</div>
</div>
{% endblock %}
42 changes: 42 additions & 0 deletions views/public/confirm-claim.html
@@ -0,0 +1,42 @@
{% extends "public/layout.html" %}
{% block head %}
<link rel="stylesheet" href="/stylesheets/criteria.css">
{% endblock %}
{% block body %}
<div class="row">
<div class="span7">
<h2>Mozilla Webmaker Badges</h2>
</div>
</div>
<div class="media card">
<img class="img badge" src="{{ badge.relativeUrl('image')}}">
<div class="bd">
<hgroup>
<span class="wordmark">Mozilla</span>
<h1>{{ badge.name }}</h1>
</hgroup>
<dl class="dl-horizontal narrow-dt">
<dt>Description:</dt>
<dd>
{% if badge.criteria.content %}
{{ badge.criteria.content | markdown }}
{% else %}
{{ badge.description | stupidSafe }}
{% endif %}
</dd>
</dl>
<hr>

<form method="post" action="/claim/confirm" class="confirm-form js-confirm-form">
<input type="hidden" name="csrf" value="{{ csrf }}">
<input type="hidden" name="code" value="{{ code }}">
<input tabindex="1" type="email" name="email" placeholder="you@example.org" class="input js-email-input" required>
<input tabindex="2" type="submit" value="Claim your badge" class="button js-submit-button">
</form>
</div>
</div>

<script src="https://beta.openbadges.org/issuer.js"></script>
<script src="/js/claim-award.js"></script>
{% endblock %}

4 changes: 2 additions & 2 deletions views/public/layout.html
Expand Up @@ -7,8 +7,9 @@
<link rel="stylesheet" href="/stylesheets/webmaker.css">
<link href="//www.mozilla.org/tabzilla/media/css/tabzilla.css" rel="stylesheet">
<!--[if lt IE 9]>
<script src="/js/html5shiv.js"></script>
<script src="/js/html5shiv.js"></script>
<![endif]-->
<script src="/js/jquery.min.js"></script>
{% block head %}{% endblock %}
</head>
<body>
Expand Down Expand Up @@ -77,7 +78,6 @@ <h3>Support our work</h3>
</div>
</footer>
</div>
<script src="//www.mozilla.org/media/js/libs/jquery-1.7.1.min.js"></script>
<script src="//www.mozilla.org/tabzilla/media/js/tabzilla.js"></script>
{% block scripts %}{% endblock %}
</body>
Expand Down

0 comments on commit 09a6ed7

Please sign in to comment.