diff --git a/src/App.vue b/src/App.vue
index 528b09f..862baa4 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,12 +1,26 @@
-
+
+
diff --git a/src/components/ui/NavMenu.vue b/src/components/ui/NavMenu.vue
index f340fe4..fb486da 100644
--- a/src/components/ui/NavMenu.vue
+++ b/src/components/ui/NavMenu.vue
@@ -1,8 +1,19 @@
@@ -16,25 +27,23 @@ const showDrawer = ref(false);
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/composables/useAuth.ts b/src/composables/useAuth.ts
new file mode 100644
index 0000000..63333c7
--- /dev/null
+++ b/src/composables/useAuth.ts
@@ -0,0 +1,54 @@
+import { ref, computed } from 'vue';
+import { Models } from 'appwrite';
+
+import { account } from '@/appwrite';
+
+const user = ref | null>(null);
+const userLoadingFinished = ref(false);
+const isLoggedIn = computed(() => !!user.value);
+
+export function useAuth() {
+ const logIn = (provider: 'github' | 'discord') => {
+ const redirectPath = localStorage.getItem('redirectPath') || '/';
+ const permissionScopes =
+ provider === 'github'
+ ? ['read:user', 'user:email']
+ : ['identify', 'email'];
+
+ account.createOAuth2Session(
+ provider,
+ `${location.origin}${redirectPath}`,
+ `${location.origin}/log-in#oauth-error`,
+ permissionScopes
+ );
+ };
+
+ const loadUser = async () => {
+ try {
+ if (userLoadingFinished.value) return;
+
+ const currentUser = await account.get();
+ user.value = currentUser;
+ } catch (error) {
+ // TODO: Add to globaL Vue error logger and implement snackbar message bus
+ console.error(error);
+ user.value = null;
+ } finally {
+ userLoadingFinished.value = true;
+ }
+ };
+
+ const logOut = async () => {
+ await account.deleteSession('current');
+ user.value = null;
+ };
+
+ return {
+ user,
+ userLoadingFinished,
+ isLoggedIn,
+ logIn,
+ loadUser,
+ logOut
+ };
+}
diff --git a/src/guards/auth.ts b/src/guards/auth.ts
new file mode 100644
index 0000000..2c5a1e6
--- /dev/null
+++ b/src/guards/auth.ts
@@ -0,0 +1,19 @@
+import { RouteLocationNormalized, NavigationGuardNext } from 'vue-router';
+import { useAuth } from '@/composables/useAuth';
+
+export async function authGuard(
+ to: RouteLocationNormalized,
+ from: RouteLocationNormalized,
+ next: NavigationGuardNext
+) {
+ const { isLoggedIn, loadUser } = useAuth();
+
+ await loadUser();
+
+ if (!to.meta.authRequired || isLoggedIn.value) {
+ return next();
+ } else {
+ localStorage.setItem('redirectPath', to.fullPath);
+ return next('/log-in');
+ }
+}
diff --git a/src/router/index.ts b/src/router/index.ts
index 699436a..2748595 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -1,4 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router';
+
+import { authGuard } from '@/guards/auth';
import Home from '@/views/Home.vue';
const routes = [
@@ -6,6 +8,17 @@ const routes = [
path: '/',
name: 'Home',
component: Home
+ },
+ {
+ path: '/log-in',
+ name: 'LogIn',
+ component: () => import('../views/LogIn.vue')
+ },
+ {
+ path: '/vote',
+ name: 'Vote',
+ meta: { authRequired: true },
+ component: () => import('../views/Vote.vue')
}
];
@@ -14,4 +27,6 @@ const router = createRouter({
routes
});
+router.beforeEach(authGuard);
+
export default router;
diff --git a/src/types.d.ts b/src/types.d.ts
new file mode 100644
index 0000000..5a54ebf
--- /dev/null
+++ b/src/types.d.ts
@@ -0,0 +1,7 @@
+import 'vue-router';
+
+declare module 'vue-router' {
+ interface RouteMeta {
+ authRequired?: boolean;
+ }
+}
diff --git a/src/views/LogIn.vue b/src/views/LogIn.vue
new file mode 100644
index 0000000..6af4cec
--- /dev/null
+++ b/src/views/LogIn.vue
@@ -0,0 +1,21 @@
+
+
+
+
+ Log in via:
+
+
+
+ GitHub
+
+
+ Discord
+
+
+
+
+
diff --git a/src/views/Vote.vue b/src/views/Vote.vue
new file mode 100644
index 0000000..b972ecf
--- /dev/null
+++ b/src/views/Vote.vue
@@ -0,0 +1,3 @@
+
+ Vote
+