Setting up User Profiles and Authentication on Webflow— Tutorial Part 2

Building Web Applications on Webflow using custom Javascript

Custom Code Embed on Webflow using VueJS Directives

A quick recap from authentication on Webflow

<!-- FILESTACK CDN -->
<script src="https://static.filestackapi.com/v3/filestack.js "></script>
<!-- VUEJS CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.min.js"></script>
<!-- GLOBAL EIGHTBASE MODULE -->
<script defer>
/* Accessible globally on webflow site */
window.EightBase = (config => {
/**
* Helper object for handling local storage. This will allow us
* to easily set and retrieve user data and authentication data
* in the browser's localStorage object.
*/
const store = {
/* Access value in localStorage */
get: key => {
return JSON.parse(localStorage.getItem(key))
},
/* Set value in localStorage */
set: (key, value) => {
localStorage.setItem(key, JSON.stringify(value))
return value
},
/* Remove Item from localStorage */
remove: key => {
localStorage.removeItem(key)
},
/* Clear values from localStorage */
clear: () => {
localStorage.clear()
},
/* Helper for determining if user is authenticated */
isAuthenticated: () => {
let auth = JSON.parse(localStorage.getItem('auth'))
return Boolean(auth) && Boolean(auth.idToken)
}
}
/**
* Whenever a page loads, this piece of code will look for
* whether the current route is private (as defined in the
* config object). If it is private and the user is not
* authenticated, it will redirect the user to the logoutRedirect
* route (also defined in the config object).
*/
let isProtectedRoute = config.routes.private.some(p =>
window.location.pathname.match(p)
)
if (isProtectedRoute && !store.isAuthenticated()) {
window.location.replace(config.routes.logoutRedirect)
}
/**
* API is a module that we'll use to execute calls to
* the 8base API. Webflow includes jQuery by default,
* and Ajaxs will work great for a graphQL Client.
*/
const api = {
request: (opts = {}) => {
return $.ajax(
Object.assign(
{
type: 'POST',
url: config.endpoint,
contentType: 'application/json',
/**
* Unless overridden, the idToken will get retrieved
* from localStorage before ever request and set as
* a bearer token.
*/
beforeSend: xhr => {
var { idToken } = store.get('auth')
xhr.setRequestHeader('Authorization', 'Bearer ' + idToken)
}
},
opts
)
)
}
}
return {
config,
store,
api
}
})({
/**
* !!!CONFIG!!!
*
* This object supplies some required info to the module.
* You're
*/
endpoint: '<PUT_YOUR_8BASE_API_ENDPOINT>',
authProfileId: '<PUT_YOUR_8BASE_AUTH_PROFILE_ID>',
routes: {
loginRedirect: '/profile',
logoutRedirect: '/sign-in',
private: ['/profile']
}
})
</script><script src=”https://static.filestackapi.com/v3/filestack.js “></script>

Creating a user profile page on Webflow

HTML Embed code editor for Webflow User Profile
<div class="form-block w-form" style="min-width: 500px;">
<!--
Unique ID for our HTML div.
-->
<div id="profile-page" name="profile-page" data-name="Profile Page">
<div class="div-block-2">
<!--
- :src -
If the user has an avatar, use the avatar.
If not, use the default avatar
- @click -
When the element is clicked, call the
handleAvatarClick method.
-->
<img
:src="user.avatar ? user.avatar.downloadUrl : defaultAvatar"
alt=""
loading="lazy"
class="image-2"
@click="handleAvatarClick"
/>
</div>
<label for="name">First&nbsp;Name</label><!--
- v-model -
Tags with v-model directive dynamically map the declared
object key to the data object in the component.
-->
<input
id="name"
type="text"
name="name"
placeholder=""
maxlength="256"
class="w-input"
data-name="Name"
v-model="user.firstName"
/>
<label for="name-2">Last&nbsp;Name</label>
<input
type="text"
maxlength="256"
name="name-2"
data-name="Name 2"
placeholder=""
id="name-2"
class="w-input"
v-model="user.lastName"
/>
<label for="email">Email</label>
<input
type="email"
maxlength="256"
name="email"
data-name="Email"
placeholder=""
id="email"
required="required"
class="w-input"
v-model="user.email"
/>
<label for="Bio">Bio</label>
<textarea
placeholder="Tell us about yourself..."
maxlength="5000"
id="Bio"
name="Bio"
data-name="Bio"
class="w-input"
v-model="user.bio"
></textarea>
<label for="email-2">Roles</label>
<ul role="list">
<!--
- v-for -
This v-for directive iterates over an array of items
and creates an html element for eact
- :key -Each element in a v-for loop requires a unique key.- v-text -The text or string value given to v-text will be displayed
within the list item element.
-->
<li v-for="role in user.roles.items" :key="role.id" v-text="role.name" />
</ul>
<!--
@click
When save button is clicked, a method called
updateUserData will be called.
-->
<input
type="submit"
value="Update"
data-wait="Please wait..."
class="w-button"
@click="updateUserData"
/>
</div>
</div>
;(function () {
new Vue({
//
//Component mounts on our div with the ID "profile-page"
//
el: '#profile-page',
data: {
errors: [],
//
// This is what our user data object looks like.
//
user: {
id: '',
bio: '',
email: '',
lastName: '',
firstName: '',
avatar: undefined,
roles: {
items: []
}
},
//
// Required credentials for uploading images.
//
fileUploadInfo: {},
//
// If the user doesn't have an avatar, we'll use this default one.
//
defaultAvatar:
'https://thumbs.dreamstime.com/b/default-avatar-profile-image-vector-social-media-user-icon-potrait-182347582.jpg',
//
// Our query for getting the user data
//
query: `
query {
user {
id
bio
email
lastName
firstName
roles {
items {
id
name
}
}
avatar {
downloadUrl
}
}
fileUploadInfo {
policy
signature
apiKey
path
}
}
`,
//
// Our mutation for updating the user data.
//
mutation: `
mutation($data: UserUpdateInput!) {
userUpdate(data: $data) {
id
}
}
`,
//
// Avatar update mutation
//
updateAvatarMutation: `
mutation($userId: ID!, $fileId: String!, $filename: String!) {
userUpdate(data: {
id: $userId
avatar: {
create: {
public: true,
fileId: $fileId,
filename: $filename,
}
}
}) {
avatar {
downloadUrl
}
}
}
`
},
computed: {
//
// Formats the form data for a profile update
//
form () {
var form = Object.assign({}, { data: this.user })
delete form.data.roles
delete form.data.avatar
return form
}
},
methods: {
//
// Gets the user data from the API and sets it as
// the current user data.
//
getUserData () {
EightBase.api.request({
data: JSON.stringify({ query: this.query }),
success: result => {
this.user = result.data.user
this.fileUploadInfo = result.data.fileUploadInfo
}
})
},
//
// Perform update on user data.
//
updateUserData (event) {
if (event) event.preventDefault()
if (event) event.stopPropagation()
EightBase.api.request({
data: JSON.stringify({
query: this.mutation,
variables: this.form
}),
success: this.getUserData
})
},
//
// Open file viewer on avatar click.
//
handleAvatarClick () {
//
// Initialize the filestack SDK
//
const client = filestack.init(this.fileUploadInfo.apiKey, {
security: {
policy: this.fileUploadInfo.policy,
signature: this.fileUploadInfo.signature
}
});
//
// Open the picker using appropriate file upload options
//
client.picker({
fromSources: ["local_file_system", "instagram", "facebook"],
storeTo: {
location: 's3',
path: this.fileUploadInfo.path
},
onFileSelected: file => {
if (file.size > 1000 * 1000) {
throw new Error('File too big, select something smaller than 1MB');
}
},
onUploadDone: this.updateUserAvatar
}).open();
},
//
// After file is uploaded, update the user with the record.
//
updateUserAvatar (result) {
const file = result.filesUploaded[0]
EightBase.api.request({
data: JSON.stringify({
query: this.updateAvatarMutation,
variables: {
fileId: file.handle,
userId: this.user.id,
filename: file.filename
}
}),
success: result => {
if (result.errors) {
alert('Upload failed! ', result)
console.log(result)
return;
}
this.user.avatar = {
downloadUrl: result.data.userUpdate.avatar.downloadUrl
}
}
})
},
//
// Logout the user (we'll perform a Logout from the profile page)
//
logout (event) {
if (event) event.preventDefault()
if (event) event.stopPropagation()
EightBase.store.clear() window.location.replace(EightBase.config.routes.logoutRedirect) return false
}
},
//
// Go fetch the user data once the API
// is created.
//
mounted () {
this.getUserData()
}
})
})();

Conclusion

‍Filestack uploader in Webflow for user profile avatars

Loves art, writing, and code.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store