-
Notifications
You must be signed in to change notification settings - Fork 1
07. User profiles and publishes
In 1batch, there are two URLs for profiles:
/profile - your own profile / editing space
/profile/mapmeld - the profile for another user ("mapmeld"). If you go to your own profile with this URL, you should be redirected back to /profile
These are two separate router.get() calls in app.js. The simpler one is another user's profile, because we only request public information, and we don't require you to be logged in at all:
// someone else's profile
router.get('/profile/:username', async function (ctx) {
var requser = ctx.req.user;
if (requser && ctx.params.username.toLowerCase() === requser.name) {
// redirect to your own profile
return ctx.redirect('/profile');
}
if (ctx.params.username.indexOf('@') > -1) {
// usernames which are still emails are anonymous
return printNoExist(ctx);
}
// we're looking for the one user with this name (there should only be one)
// the only public information that's interesting to us is their _id, username, and whether they publicly posted yet
// your app may need more fields to be public
var user = await User.findOne({ name: req.params.username.toLowerCase() }, '_id username posted').exec();
if (!user) { throw 'user does not exist'; }
// request all images which are published by this user, get source urls
var images = await Image.find({ published: true, hidden: false, user_id: user.name }).select('_id src').exec();
// there's a function called responsiveImg which returns proper URLs from Cloudinary
// we'll talk about it later
images = images.map(responsiveImg);
ctx.render('profile', {
user: user, // the user who created this profile
images: images, // the user's images
posted: user.posted, // the user's post date (if they posted)
forUser: (requser || null), // whether the browsing user is signed in or not
csrfToken: ctx.csrf
});
});responsiveImg is going to be used several times. It takes in an array of Image objects, and asks Cloudinary to return formatted URLs for that image. It also requests the image in multiple sizes, which we can use to show people this page on desktop, mobile, or retina screens. responsiveImg looks like this:
function responsiveImg(img, makeBig) {
var baseSize = 300;
if (makeBig) {
baseSize *= 2;
}
var geturl = cloudinary.url;
var out = {
_id: img._id,
src: {
mini: geturl(img.src, { format: "jpg", width: baseSize * 2/3, height: baseSize * 2/3, crop: "fill" }).replace('http:', ''),
main: geturl(img.src, { format: "jpg", width: baseSize, height: baseSize, crop: "fill" }).replace('http:', ''),
retina: geturl(img.src, { format: "jpg", width: baseSize * 2, height: baseSize * 2, crop: "fill" }).replace('http:', '')
}
};
return out;
}Another detail is we remove 'http:' from the Cloudinary URL. Browsers can throw errors when an HTTPS page has an HTTP resource, and vice versa. Using // means to keep the same protocol as the current page.
Now we can design the public profile page itself, using a Jade template:
extends ./layout.jade
block content
.row
.col-sm-12.underbar
if forUser
a.btn.btn-info.pull-right(href="/logout") Log Out
else
a.btn.btn-primary.pull-right(href="/profile") Log In
h2
a(href="/feed") 1batch
small an app for sharing 8 photos
.row
.col-sm-12.col-md-6
h1= user.name
if posted
h4 posted #{posted}
else
h4 hasn't posted yet!
if posted
.row.profilephotos
for index in [0, 1, 2, 3]
if index < images.length
.col-sm-6.col-lg-3
img(src=images[index].src.main, srcset="#{images[index].src.main} 1x, #{images[index].src.retina} 2x")
.row.profilephotos
for index in [4, 5, 6, 7]
if index < images.length
.col-sm-6.col-lg-3
img(src=images[index].src.main, srcset="#{images[index].src.main} 1x, #{images[index].src.retina} 2x")We're using Bootstrap CSS, so the page is divided up into several divs using the .row class.
The first row has a Log In or Log Out button, depending on the current status of the user.
That's followed by the username and the phrase "posted {time}" or "hasn't posted yet!"
The image rows are more complex. Bootstrap divides the page up into a grid with 12 units. The class col-sm-6 means that at a small page size, the photos should take up six units (half the page), and col-lg-3 means that a larger page size, the photos should take up three (a quarter of the page). At the smallest size, Bootstrap automatically reduces the page to a single column.
If you're familiar with HTML, you'll recognize that we're setting the image's src attribute. We also use the newer srcset to mention the retina-resolution image, and let the device decide which to use. The srcset attribute and techniques for supporting it on older devices is explained here: http://www.sitepoint.com/how-to-build-responsive-images-with-srcset/
Now we just need to create our own profile page, with editing abilities. In 1batch I currently have this as one Jade view, but I'll explain it as a separate view (and possibly implement it this way later on)
extends ./layout.jade
block content
.row
.col-sm-12.underbar
a.btn.btn-info.pull-right(href="/logout") Log Out
h2
a(href="/feed") 1batch
small an app for sharing 8 photos
.row
.col-sm-12.col-md-6
h1= user.name
if posted
h4 posted #{posted}
else
h4 You haven't posted yet!
if posted
.row
.col-sm-12
.well
p Click a photo to go to its photo page. You can leave comments, hide it, or remove it.
.row.profilephotos
for index in [0, 1, 2, 3]
if index < images.length
.col-sm-6.col-lg-3
a.pcon(href="/#{user.name}/photo/#{images[index]._id}")
img(src=images[index].src.main, srcset="#{images[index].src.main} 1x, #{images[index].src.retina} 2x")
.row.profilephotos
for index in [4, 5, 6, 7]
if index < images.length
.col-sm-6.col-lg-3
a.pcon(href="/#{user.name}/photo/#{images[index]._id}")
img(src=images[index].src.main, srcset="#{images[index].src.main} 1x, #{images[index].src.retina} 2x")
if (!posted || user.republish)
.row
.col-sm-12.col-md-6
form.well(action="/upload?_csrf=#{csrfToken}", method="POST", enctype="multipart/form-data")
label Upload a photo that you might use. You can change your mind before posting.
hr
input(type="file", name="upload")
button.btn.btn-success.pull-right Upload
.clearfix
.row
.col-sm-12.savedphotos
if saved.length
h4
span Stored images (
span.pickcount
span / 8 picked)
small double-tap to pick
for image in saved
.col-sm-6.col-lg-4
a.pcon(href="javascript:void(0);", class="#{image.picked ? 'picked' : ''}")
img(id=image._id, src=image.src.main, srcset="#{image.src.main} 1x, #{image.src.retina} 2x")
span.pick
if !image.picked
button Pick ✓
else
button Remove x
else
p No images uploaded yet...
if saved.length
.row
.col-sm-12.postnow.well
p Once you've picked 1 to 8 photos that you really like, hit publish!
ul
li Your photos will be online for six months
li You can always hide or delete a photo
li You have one hour to redo if you change your mind
br
button.btn.btn-success
span Publish Picked Photos (
span.pickcount
span )
block scripts
script(type="text/javascript", src="/lib/jquery-1.11.3.min.js")
if forUser && user.name == forUser.name
script(type="text/javascript", src="/image-select.js")