-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
291 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './AccountService'; | ||
export * from './firebaseAccountService'; | ||
export * from './LocalAccountService'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './accounts'; |