-
Notifications
You must be signed in to change notification settings - Fork 13
feat: implement Timeline page with activity log and infinite scroll functionality #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
abda1a3
f0f8b2e
b8c6ab9
8d226c8
674993c
3f62f29
4451b95
ded88f8
38f46b3
405ee5b
2bcae86
597f069
be4a438
850383c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,3 +30,4 @@ blob-report/ | |
| playwright/.cache/ | ||
|
|
||
|
|
||
| *.code-workspace | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <script setup lang="ts"> | ||
| import { History } from 'lucide-vue-next' | ||
|
|
||
| const props = defineProps<{ | ||
| date: string | Date | ||
| }>() | ||
|
|
||
| const localePath = useLocalePath() | ||
|
|
||
| const timelineUrl = computed(() => { | ||
| const d = typeof props.date === 'string' ? props.date : props.date.toISOString() | ||
| const dateKey = d.slice(0, 10) | ||
| return localePath(`/dashboard/timeline?date=${dateKey}`) | ||
| }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <NuxtLink | ||
| :to="timelineUrl" | ||
| class="group/tl inline-flex items-center gap-1 transition-colors hover:text-brand-600 dark:hover:text-brand-400" | ||
| title="View in timeline" | ||
| > | ||
| <slot /> | ||
| <History class="size-3 opacity-0 group-hover/tl:opacity-60 transition-opacity shrink-0" /> | ||
| </NuxtLink> | ||
| </template> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -48,13 +48,23 @@ export async function usePostHogIdentity() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Watch org AND consent for group analytics — same gating logic as above. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| watch( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [() => activeOrgState.value?.data, hasConsented] as const, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ([org, consented]) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async ([org, consented]) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (consented) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (org?.id) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Only org id and name are forwarded; slug is omitted to minimise data. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;($posthogSetOrganization as (org: { id: string, name?: string }) => void)({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fetch the current member's role to enrich group properties — useful | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // for debugging permission issues without exposing personal data. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let memberRole: string | undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await authClient.organization.getActiveMemberRole() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| memberRole = data?.role ?? undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| catch { /* non-critical; role is just an enrichment property */ } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Only org id, name, and member role are forwarded; slug is omitted to minimise data. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;($posthogSetOrganization as (org: { id: string, name?: string, member_role?: string }) => void)({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: org.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: org.name || undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| member_role: memberRole, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+51
to
68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent stale organization attribution from out-of-order async watcher runs. At Line 58, the awaited role fetch can resolve after org/consent changes, and Line 64 can then send a stale org payload to PostHog. This can misattribute events to the wrong organization. Suggested fix watch(
[() => activeOrgState.value?.data, hasConsented] as const,
- async ([org, consented]) => {
+ async ([org, consented], _prev, onCleanup) => {
+ let cancelled = false
+ onCleanup(() => { cancelled = true })
+
if (consented) {
if (org?.id) {
+ const orgId = org.id
// Fetch the current member's role to enrich group properties — useful
// for debugging permission issues without exposing personal data.
let memberRole: string | undefined
try {
const { data } = await authClient.organization.getActiveMemberRole()
memberRole = data?.role ?? undefined
}
catch { /* non-critical; role is just an enrichment property */ }
+ if (cancelled) return
+ if (!hasConsented.value) return
+ if (activeOrgState.value?.data?.id !== orgId) return
+
// Only org id, name, and member role are forwarded; slug is omitted to minimise data.
;($posthogSetOrganization as (org: { id: string, name?: string, member_role?: string }) => void)({
- id: org.id,
+ id: orgId,
name: org.name || undefined,
member_role: memberRole,
})📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
document_downloadedis emitted before confirming success.At Line 250, failures still produce a “downloaded” event. Emit after
await downloadDocument(docId)or rename todocument_download_requested.Suggested fix
async function handleDownload(docId: string) { try { - track('document_downloaded', { document_id: docId }) await downloadDocument(docId) + track('document_downloaded', { document_id: docId }) } catch { toast.error('Failed to download document') } }📝 Committable suggestion
🤖 Prompt for AI Agents