Skip to content

Commit

Permalink
working login
Browse files Browse the repository at this point in the history
  • Loading branch information
aklinker1 committed Apr 25, 2024
1 parent dd3e277 commit e374a49
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 0 deletions.
10 changes: 10 additions & 0 deletions web/components/ProfileTab.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script lang="ts" setup>
const currentUser = useCurrentUser();
</script>

<template>
<TabListItem to="/account" hide-close>
<span v-if="!currentUser" class="text-sm">Sign In</span>
<UIcon name="i-heroicons-user-circle" class="size-6" />
</TabListItem>
</template>
11 changes: 11 additions & 0 deletions web/composables/useAccountService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const firebase = createFirebaseAccountService();
const local = createLocalAccountService();

export function useAccountService() {
const user = useCurrentUser();

return computed(() => {
if (user.value == null) return local;
return firebase;
});
}
10 changes: 10 additions & 0 deletions web/composables/useCurrentUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default createSharedComposable(() => {
const user = ref(firebaseAuth.currentUser);
firebaseAuth.authStateReady().then(() => {
user.value = firebaseAuth.currentUser;
});
firebaseAuth.onAuthStateChanged(() => {
user.value = firebaseAuth.currentUser;
});
return user;
});
12 changes: 12 additions & 0 deletions web/composables/useSaveSettingsMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useMutation } from '@tanstack/vue-query';
import type { AccountSettings } from '~/utils';

export default function () {
const accountService = useAccountService();

return useMutation({
mutationFn(changes: Partial<AccountSettings>) {
return accountService.value.setSettings(changes);
},
});
}
63 changes: 63 additions & 0 deletions web/pages/account.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script lang="ts" setup>
import * as firebaseui from 'firebaseui';
import { EmailAuthProvider } from 'firebase/auth';
import 'firebaseui/dist/firebaseui.css';
const currentUser = useCurrentUser();
const container = ref<HTMLDivElement>();
let login: firebaseui.auth.AuthUI | undefined;
watch(container, (container) => {
if (container) {
login =
firebaseui.auth.AuthUI.getInstance() ??
new firebaseui.auth.AuthUI(firebaseAuth);
login.start(container, {
signInOptions: [EmailAuthProvider.PROVIDER_ID],
});
} else {
login?.delete();
login = undefined;
}
});
watch(currentUser, (user) => {
if (user) syncProjects();
});
async function syncProjects() {
const localService = createLocalAccountService();
const firebaseService = createFirebaseAccountService();
const localProjects = await localService.listProjects();
const firebaseProjects = await firebaseService.listProjects();
const firebaseProjectIds = new Set(firebaseProjects.map((p) => p.id));
const missingProjects = localProjects.filter(
(p) => !firebaseProjectIds.has(p.id),
);
console.log(
`Syncing ${missingProjects.length} local projects to firebase...`,
);
for (const p of missingProjects) {
await firebaseService.saveProject(p);
}
}
</script>

<template>
<div class="bg-gray-200 dark:bg-gray-800 h-full p-8 flex flex-col gap-4">
<h2 class="text-2xl font-medium line-clamp-1 truncate">Account</h2>
<template v-if="!currentUser">
<p>Login to share settings and projects between devices</p>
<div ref="container" v-if="!currentUser" />
</template>
<template v-else>
<p>
Logged in as: {{ currentUser.displayName }} ({{ currentUser.email }})
</p>
<UButton class="self-start" @click="firebaseAuth.signOut()"
>Log out</UButton
>
</template>
</div>
</template>
25 changes: 25 additions & 0 deletions web/utils/accounts/AccountService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Project } from '../projects';

export interface AccountService {
getSettings(): Promise<AccountSettings>;
setSettings(changes: Partial<AccountSettings>): Promise<void>;
listProjects(): Promise<Project[]>;
saveProject(newProject: Project): Promise<void>;
removeProject(id: string): Promise<void>;
}

export interface AccountSettings {
bladeWidth: number;
distanceUnit: string;
extraSpace: number;
optimize: 'Cuts' | 'Space';
showPartNumbers: boolean;
}

export const DEFAULT_SETTINGS: AccountSettings = {
bladeWidth: 0.125,
distanceUnit: 'in',
extraSpace: 0,
optimize: 'Cuts',
showPartNumbers: true,
};
46 changes: 46 additions & 0 deletions web/utils/accounts/FirebaseAccountService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
getDoc,
setDoc,
getDocs,
query,
deleteDoc,
collection,
doc,
} from 'firebase/firestore';
import { db, firebaseAuth, usersRef } from '../firebase';
import { DEFAULT_SETTINGS } from './AccountService';

export function createFirebaseAccountService(): AccountService {
const getUid = () => {
if (firebaseAuth.currentUser == null) throw Error('Not logged in');
return firebaseAuth.currentUser.uid;
};

const settingsDoc = () => doc(usersRef, getUid(), 'settings', 'default');
const projectsRef = () => collection(usersRef, getUid(), 'projects');
const projectDoc = (id: string) => doc(usersRef, getUid(), 'projects', id);

return {
async getSettings() {
const res = await getDoc(settingsDoc());
return {
...DEFAULT_SETTINGS,
...(res.exists() ? res.data() : {}),
};
},
async setSettings(changes) {
await setDoc(settingsDoc(), changes, { merge: true });
},
async listProjects() {
const q = query(projectsRef());
const res = await getDocs(q);
return res.docs.map((doc) => doc.data()) as Project[];
},
async saveProject(project) {
await setDoc(projectDoc(project.id), project);
},
async removeProject(projectId) {
await deleteDoc(projectDoc(projectId));
},
};
}
93 changes: 93 additions & 0 deletions web/utils/accounts/LocalAccountService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { Project } from '../projects';
import {
DEFAULT_SETTINGS,
type AccountService,
type AccountSettings,
} from './AccountService';

export function createLocalAccountService(): AccountService {
const settingsStorageKeys: Record<keyof AccountSettings, string> = {
bladeWidth: '@cutlist/blade-width',
distanceUnit: '@cutlist/distance-unit',
extraSpace: '@cutlist/extra-space',
optimize: '@cutlist/optimize',
showPartNumbers: '@cutlist/use-part-numbers',
};
const projectsStorageKey = '@cutlist/projects';

const parseNumber = (str: string | null): number | undefined => {
const v = Number(str ?? undefined);
if (isNaN(v)) return undefined;
return v;
};
const parseBoolean = (str: string | null): boolean | undefined => {
if (str == null) return undefined;
return str === 'true';
};

const getSettings = (): AccountSettings => {
const settings: Partial<AccountSettings> = {};

const bladeWidth = parseNumber(
localStorage.getItem(settingsStorageKeys.bladeWidth),
);
if (bladeWidth != null) settings.bladeWidth = bladeWidth;

const extraSpace = parseNumber(
localStorage.getItem(settingsStorageKeys.extraSpace),
);
if (extraSpace != null) settings.extraSpace = extraSpace;

const optimize = localStorage.getItem(settingsStorageKeys.optimize);
if (optimize != null)
settings.optimize = optimize as AccountSettings['optimize'];

const showPartNumbers = parseBoolean(
localStorage.getItem(settingsStorageKeys.showPartNumbers),
);
if (showPartNumbers != null) settings.showPartNumbers = showPartNumbers;

const distanceUnit = localStorage.getItem(settingsStorageKeys.distanceUnit);
if (distanceUnit != null) settings.distanceUnit = distanceUnit;

return {
...DEFAULT_SETTINGS,
...settings,
};
};

const listProjects = (): Project[] => {
const str = localStorage.getItem(projectsStorageKey);
return str ? JSON.parse(str) : [];
};
const saveProjects = (projects: Project[]) => {
localStorage.setItem(projectsStorageKey, JSON.stringify(projects));
};

return {
async getSettings() {
return getSettings();
},
async setSettings(changes) {
Object.entries(changes).forEach(([key, value]) => {
const localStorageKey =
settingsStorageKeys[key as keyof AccountSettings];
if (!localStorageKey) return;

localStorage.setItem(localStorageKey, String(value));
});
},
async listProjects() {
return listProjects();
},
async removeProject(id) {
const newProjects = listProjects().filter((p) => p.id !== id);
saveProjects(newProjects);
},
async saveProject(newProject) {
const newProjects = listProjects();
newProjects.push(newProject);
saveProjects(newProjects);
},
};
}
3 changes: 3 additions & 0 deletions web/utils/accounts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './AccountService';
export * from './firebaseAccountService';
export * from './LocalAccountService';
17 changes: 17 additions & 0 deletions web/utils/firebase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { initializeApp } from 'firebase/app';
import { getFirestore, collection, doc } from 'firebase/firestore';
import { getAuth } from 'firebase/auth';

export const firebaseApp = initializeApp({
apiKey: 'AIzaSyBKkmNsLYzz12hNoGMFP3eaHCtxPJCsIi0',
authDomain: 'cutlist-17450.firebaseapp.com',
projectId: 'cutlist-17450',
storageBucket: 'cutlist-17450.appspot.com',
messagingSenderId: '149400144857',
appId: '1:149400144857:web:fa6f4a90ec177fd90ce7c8',
});

export const db = getFirestore(firebaseApp);
export const usersRef = collection(db, 'users');

export const firebaseAuth = getAuth(firebaseApp);
1 change: 1 addition & 0 deletions web/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './accounts';

0 comments on commit e374a49

Please sign in to comment.