Vue JS Workout Application Plus SupaBase Backend
by Keith Rowles • 01/01/2024Vue
Summary
A full CRUD Vue JS workout application using Supabase for the authentication and data storage.
Using Vue 3 composition API instead of the options API.
The application is an interactive website where you can register and login.
You can create, read, update and delete workouts.
Note: You can view all workouts… but only edit and delete workouts that you have created.
SupaBase is used for the backend for user authentication and a database to store workout information.
The project has been deployed to Netlify.
Tech and Tools
- HTML
- CSS
- JavaScript
- Vue
- Vue Router
- SupaBase
- Vite
- Tailwind CSS
- Post CSS
- SASS
- Netlify
- Uid
- Environment Variables
Sample Code
Please view my GitHub repository for full code. Link provided at bottom of the page.
App.vue
<template>
<div class="min-h-full font-Poppins box-border" v-if="appReady">
<Navigation />
<router-view />
</div>
</template>
<script>
import Navigation from './components/Navigation.vue';
import { onMounted, ref } from 'vue';
import { supabase } from './supabase/init';
import store from './store/index';
export default {
components: { Navigation },
setup() {
// Create data / vars
const appReady = ref(null);
// Check to see if user is already logged in
onMounted(async () => {
try {
const {
data: { user },
} = await supabase.auth.getUser();
// If user does not exist, need to make app ready
if (!user) {
appReady.value = true;
}
// Runs when there is a auth state change
// if user is logged in, this will fire
supabase.auth.onAuthStateChange((_, session) => {
console.log('hello', session);
store.methods.setUser(session);
appReady.value = true;
});
} catch (error) {
console.error(error);
}
});
return { appReady };
},
};
</script>
<style lang="scss">
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700&display=swap');
</style>
Login.vue
<template>
<div class="max-w-screen-sm mx-auto px-4 py-10">
<!-- error handling -->
<div
v-if="errorMessage"
class="mb-10 p-4 rounded-md bg-light-grey shadow-lg"
>
<p class="text-red-500">{{ errorMessage }}</p>
</div>
<!-- login form -->
<form
class="p-8 flex flex-col bg-light-grey rounded-md shadow-lg"
@submit.prevent="login"
>
<h1 class="text-3xl text-at-light-green mb-4">Login</h1>
<div class="flex flex-col mb-2">
<label for="email" class="mb-1 text-sm text-at-light-green"
>Email</label
>
<input
type="text"
required
class="p-2 text-gray-500 focus:outline-none"
id="email"
v-model="email"
/>
</div>
<div class="flex flex-col mb-2">
<label for="password" class="mb-1 text-sm text-at-light-green"
>Password</label
>
<input
type="password"
required
class="p-2 text-gray-500 focus:outline-none"
id="password"
v-model="password"
/>
</div>
<button
type="submit"
class="mt-6 py-2 px-6 rounded-sm self-start text-sm text-white bg-at-light-green duration-200 border-solid border-2 border-transparent hover:border-at-light-green hover:bg-white hover:text-at-light-green"
>
Login
</button>
<router-link class="text-sm mt-6 text-center" :to="{ name: 'Register' }"
>Don't have an account?
<span class="text-at-light-green">Register</span></router-link
>
</form>
</div>
</template>
<script>
import { ref } from 'vue';
import { supabase } from '../supabase/init';
import { useRouter } from 'vue-router';
export default {
name: 'login',
setup() {
// Create data / vars
const router = useRouter();
const email = ref(null);
const password = ref(null);
const errorMessage = ref(null);
// Login function
const login = async () => {
try {
const { error } = await supabase.auth.signInWithPassword({
email: email.value,
password: password.value,
});
if (error) throw error;
router.push({ name: 'Home' });
} catch (error) {
errorMessage.value = `Error: ${error.message}`;
setTimeout(() => {
errorMessage.value = null;
}, 5000);
}
};
return { email, password, errorMessage, login };
},
};
</script>
Demo
Open demo on Netlify.
Link to DemoRepo
Open repository on GitHub.
Link to Demo