-
Notifications
You must be signed in to change notification settings - Fork 50
feat(web): remove auth requirement to simplify onboarding #1404
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
base: main
Are you sure you want to change the base?
Changes from 15 commits
1cc66ae
bcd9704
6cf674d
367fd91
c714c57
889f94a
82e4fe2
64a29d8
92b182a
b94777b
f151324
bb54bf1
1559e68
e5b122c
d5cf873
410794c
c4fd05c
b972901
5ac651d
5cc9ad4
77b0ba9
3601507
811ab9f
701a39a
895c81f
64d91b2
5ba256a
9e083f0
eeb824f
5f6205d
bac8b27
eb7ed12
18b7a52
f1adaa8
d322254
5351e68
cac83ab
d8b7a96
e1eebd3
92c5ef6
83ada8a
38139f8
7148725
07a4d82
cd921a6
739fa8a
6c7464a
cf2f477
5a9d041
e40ca95
76cb0fe
9a612d4
4d6cfb5
01d1b8e
05b7b0e
d2fd159
ef4667c
f0ccfb5
12ccd37
3ed7c68
0ac9183
c588194
6bb0e2a
a2d4098
3e5e37c
964dc28
a4cace0
dca5f6d
37d1ffa
ddc5928
56d1337
75fc3f4
85adcdc
15c8a78
7a1893a
2dfe7e0
b3476ea
9d51126
0abc2dd
700ce5b
217dd69
5904478
b5b6885
f15c1d1
dd7ec2b
2da6826
491220d
a846208
5598452
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 | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -76,10 +76,29 @@ export class GcalEventRRule extends RRule { | |||||||||||||||||||||||||||||||
| index < GCAL_MAX_RECURRENCES, | ||||||||||||||||||||||||||||||||
| ): Date[] { | ||||||||||||||||||||||||||||||||
| const dates = super.all(iterator); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // If no dates were generated, return empty array | ||||||||||||||||||||||||||||||||
| if (dates.length === 0) { | ||||||||||||||||||||||||||||||||
| return []; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const firstInstance = dates[0]; | ||||||||||||||||||||||||||||||||
| const firstInstanceStartDate = dayjs(firstInstance).tz(this.#timezone); | ||||||||||||||||||||||||||||||||
| const includesDtStart = this.#startDate.isSame(firstInstanceStartDate); | ||||||||||||||||||||||||||||||||
| const rDates = includesDtStart ? [] : [this.#startDate.toDate()]; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Check if dtstart is already included in the generated dates | ||||||||||||||||||||||||||||||||
| // Use a more lenient comparison to handle timezone precision issues | ||||||||||||||||||||||||||||||||
| const includesDtStart = | ||||||||||||||||||||||||||||||||
| this.#startDate.isSame(firstInstanceStartDate, "minute") || | ||||||||||||||||||||||||||||||||
| dates.some((date) => { | ||||||||||||||||||||||||||||||||
| const dateInTz = dayjs(date).tz(this.#timezone); | ||||||||||||||||||||||||||||||||
| return this.#startDate.isSame(dateInTz, "minute"); | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
| const includesDtStart = | |
| this.#startDate.isSame(firstInstanceStartDate, "minute") || | |
| dates.some((date) => { | |
| const dateInTz = dayjs(date).tz(this.#timezone); | |
| return this.#startDate.isSame(dateInTz, "minute"); | |
| }); | |
| const includesDtStart = this.#startDate.isSame( | |
| firstInstanceStartDate, | |
| "minute", | |
| ) | |
| ? true | |
| : dates.some((date) => { | |
| const dateInTz = dayjs(date).tz(this.#timezone); | |
| return this.#startDate.isSame(dateInTz, "minute"); | |
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,23 @@ | ||
| export const STORAGE_KEYS = { | ||
| import { z } from "zod"; | ||
|
|
||
| export const StorageKeySchema = z.enum([ | ||
| "compass.reminder", | ||
| "compass.auth.hasCompletedSignup", | ||
| "compass.auth.skipOnboarding", | ||
| "compass.onboarding", | ||
| ]); | ||
|
|
||
| export type StorageKey = z.infer<typeof StorageKeySchema>; | ||
|
|
||
| export const STORAGE_KEYS: Record< | ||
| | "REMINDER" | ||
| | "HAS_COMPLETED_SIGNUP" | ||
| | "SKIP_ONBOARDING" | ||
| | "ONBOARDING_PROGRESS", | ||
| StorageKey | ||
| > = { | ||
| REMINDER: "compass.reminder", | ||
| HAS_COMPLETED_SIGNUP: "compass.auth.hasCompletedSignup", | ||
| SKIP_ONBOARDING: "compass.auth.skipOnboarding", | ||
| }; | ||
| ONBOARDING_PROGRESS: "compass.onboarding", | ||
| } as const; | ||
tyler-dane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,11 @@ | ||
| import dayjs from "@core/util/date/dayjs"; | ||
| import { Task, isTask } from "@web/common/types/task.types"; | ||
| import { | ||
| getOnboardingProgress, | ||
| updateOnboardingProgress, | ||
| } from "@web/views/Onboarding/utils/onboardingStorage.util"; | ||
|
|
||
| export const TODAY_TASKS_STORAGE_KEY_PREFIX = "compass.today.tasks"; | ||
| const STORAGE_INFO_SEEN_KEY = "compass.day.storage-info-seen"; | ||
| export const COMPASS_TASKS_SAVED_EVENT_NAME = "compass.tasks.saved" as const; | ||
|
|
||
| /** | ||
|
|
@@ -104,12 +107,12 @@ export function hasSeenStorageInfo(): boolean { | |
| if (typeof window === "undefined") { | ||
| return true; | ||
| } | ||
| return localStorage.getItem(STORAGE_INFO_SEEN_KEY) === "true"; | ||
| return getOnboardingProgress().isStorageWarningSeen; | ||
| } | ||
|
|
||
| export function markStorageInfoAsSeen(): void { | ||
| if (typeof window === "undefined") { | ||
| return; | ||
| } | ||
| localStorage.setItem(STORAGE_INFO_SEEN_KEY, "true"); | ||
| updateOnboardingProgress({ isStorageWarningSeen: true }); | ||
| } | ||
|
Comment on lines
107
to
118
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import { Task } from "@web/common/types/task.types"; | ||
| import { | ||
| getDateKey, | ||
| loadTasksFromStorage, | ||
| saveTasksToStorage, | ||
| } from "@web/common/utils/storage/storage.util"; | ||
| import { seedInitialTasks } from "./task-seeding.util"; | ||
|
|
||
| // Mock localStorage | ||
| const localStorageMock = (() => { | ||
| let store: Record<string, string> = {}; | ||
|
|
||
| return { | ||
| getItem: (key: string) => store[key] || null, | ||
| setItem: (key: string, value: string) => { | ||
| store[key] = value.toString(); | ||
| }, | ||
| removeItem: (key: string) => { | ||
| delete store[key]; | ||
| }, | ||
| clear: () => { | ||
| store = {}; | ||
| }, | ||
| }; | ||
| })(); | ||
|
|
||
| Object.defineProperty(window, "localStorage", { | ||
| value: localStorageMock, | ||
| }); | ||
|
|
||
| describe("task-seeding.util", () => { | ||
| beforeEach(() => { | ||
| localStorageMock.clear(); | ||
| }); | ||
|
|
||
| describe("seedInitialTasks", () => { | ||
| it("should seed initial tasks when none exist", () => { | ||
| const dateKey = getDateKey(); | ||
| const tasks = seedInitialTasks(dateKey); | ||
|
|
||
| expect(tasks).toHaveLength(2); | ||
| expect(tasks[0].title).toBe("Review project proposal"); | ||
| expect(tasks[1].title).toBe("Write weekly report"); | ||
| expect(tasks[0].status).toBe("todo"); | ||
| expect(tasks[1].status).toBe("todo"); | ||
| expect(tasks[0].id).toBeDefined(); | ||
| expect(tasks[1].id).toBeDefined(); | ||
| }); | ||
|
|
||
| it("should return existing tasks if they already exist", () => { | ||
| const dateKey = getDateKey(); | ||
| const existingTask: Task = { | ||
| id: "existing-id", | ||
| title: "Existing task", | ||
| status: "todo", | ||
| createdAt: new Date().toISOString(), | ||
| order: 0, | ||
| }; | ||
|
|
||
| saveTasksToStorage(dateKey, [existingTask]); | ||
| const tasks = seedInitialTasks(dateKey); | ||
|
|
||
| expect(tasks).toHaveLength(1); | ||
| expect(tasks[0].id).toBe("existing-id"); | ||
| expect(tasks[0].title).toBe("Existing task"); | ||
| }); | ||
|
|
||
| it("should save tasks to localStorage", () => { | ||
| const dateKey = getDateKey(); | ||
| seedInitialTasks(dateKey); | ||
|
|
||
| const storedTasks = loadTasksFromStorage(dateKey); | ||
| expect(storedTasks).toHaveLength(2); | ||
| }); | ||
|
|
||
| it("should work with different date keys", () => { | ||
| const dateKey1 = "2024-01-01"; | ||
| const dateKey2 = "2024-01-02"; | ||
|
|
||
| const tasks1 = seedInitialTasks(dateKey1); | ||
| const tasks2 = seedInitialTasks(dateKey2); | ||
|
|
||
| expect(tasks1).toHaveLength(2); | ||
| expect(tasks2).toHaveLength(2); | ||
| expect(tasks1[0].id).not.toBe(tasks2[0].id); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { v4 as uuidv4 } from "uuid"; | ||
| import { Task } from "@web/common/types/task.types"; | ||
| import { | ||
| loadTasksFromStorage, | ||
| saveTasksToStorage, | ||
| } from "@web/common/utils/storage/storage.util"; | ||
|
|
||
| /** | ||
| * Initial task titles to seed for new users | ||
| */ | ||
| const INITIAL_TASK_TITLES = ["Review project proposal", "Write weekly report"]; | ||
tyler-dane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Seeds initial tasks for a given date if no tasks exist | ||
| * @param dateKey - Date key in format YYYY-MM-DD | ||
| * @returns Array of seeded tasks | ||
| */ | ||
| export function seedInitialTasks(dateKey: string): Task[] { | ||
| const existingTasks = loadTasksFromStorage(dateKey); | ||
|
|
||
| // If tasks already exist, return them | ||
| if (existingTasks.length > 0) { | ||
| return existingTasks; | ||
| } | ||
|
|
||
| // Create initial tasks | ||
| const initialTasks: Task[] = INITIAL_TASK_TITLES.map((title, index) => ({ | ||
| id: uuidv4(), | ||
| title, | ||
| status: "todo" as const, | ||
| createdAt: new Date().toISOString(), | ||
| order: index, | ||
| })); | ||
|
|
||
| // Save to localStorage | ||
| saveTasksToStorage(dateKey, initialTasks); | ||
|
|
||
| return initialTasks; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { Outlet } from "react-router-dom"; | ||
| import { useGlobalShortcuts } from "@web/views/Calendar/hooks/shortcuts/useGlobalShortcuts"; | ||
|
|
||
| /** | ||
| * Layout component for unauthenticated/guest users | ||
| * Provides the same global shortcuts (like cmd+k) as authenticated users | ||
| * but without requiring authentication | ||
| */ | ||
| export const GuestLayout = () => { | ||
| // Enable global shortcuts for guest users (including cmd+k palette) | ||
| useGlobalShortcuts(); | ||
|
|
||
| return <Outlet />; | ||
| }; | ||
tyler-dane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Uh oh!
There was an error while loading. Please reload this page.