-
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
21 changed files
with
381 additions
and
42 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
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,47 @@ | ||
<script lang="ts" setup> | ||
const createNewProject = useCreateNewProject(); | ||
const { data: projects, isFetching } = useProjectListQuery(); | ||
const filter = ref(''); | ||
const filteredProjects = useArrayFilter(projects, (p) => | ||
p.name.toLowerCase().includes(filter.value.toLowerCase()), | ||
); | ||
const openProject = useOpenProject(); | ||
const deleteProject = useDeleteProject(); | ||
</script> | ||
|
||
<template> | ||
<ul class="flex flex-col gap-2"> | ||
<li> | ||
<UInput | ||
v-model="filter" | ||
placeholder="Filter projects..." | ||
icon="i-heroicons-magnifying-glass" | ||
/> | ||
</li> | ||
<ProjectListItem | ||
v-for="project of filteredProjects" | ||
:key="project.id" | ||
:project | ||
@open="openProject" | ||
@delete="deleteProject" | ||
/> | ||
<li v-if="isFetching" class="flex justify-center"> | ||
<UButton | ||
class="pointer-events-none" | ||
loading | ||
variant="ghost" | ||
color="white" | ||
disabled | ||
> | ||
Loading... | ||
</UButton> | ||
</li> | ||
<li v-else-if="filteredProjects.length === 0" class="text-center py-16"> | ||
No projects, | ||
<ULink variant="link" @click="createNewProject">add one</ULink> | ||
</li> | ||
<slot /> | ||
</ul> | ||
</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
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,4 @@ | ||
export default function () { | ||
const account = useAccountService(); | ||
return computed(() => account.value.id); | ||
} |
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 { useQuery } from '@tanstack/vue-query'; | ||
|
||
export default function () { | ||
const account = useAccountService(); | ||
const accountId = useAccountServiceId(); | ||
const user = useCurrentUser(); | ||
return useQuery({ | ||
queryKey: ['projects', accountId], | ||
queryFn: () => account.value.listProjects(), | ||
initialData: [], | ||
}); | ||
} |
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
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
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
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
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,26 @@ | ||
import type { Project } from '../projects'; | ||
|
||
export interface AccountService { | ||
id: string; | ||
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,47 @@ | ||
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 { | ||
id: 'firebase', | ||
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)); | ||
}, | ||
}; | ||
} |
Oops, something went wrong.