A simple to-do app focused on tasks that can be completed within a specific time span.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

lints

+47 -51
+8 -8
app/components/Settings/SettingsSheet.vue
··· 8 8 9 9 const isOpen = defineModel<boolean>("isOpen", { required: true }); 10 10 11 + const config = useRuntimeConfig(); 12 + 11 13 function close() { 12 14 isOpen.value = false; 13 15 } ··· 23 25 } else { 24 26 console.error("Unknown locale"); 25 27 } 26 - } 28 + } 27 29 28 - async function sendPush() { 30 + async function sendPush() { 29 31 const permission = await Notification.requestPermission(); 30 32 31 33 if (permission !== "granted") return; ··· 34 36 35 37 const sub = await reg.pushManager.subscribe({ 36 38 userVisibleOnly: true, 37 - applicationServerKey: 38 - process.env.SUBSCRIPTIONS_PUBLIC_KEY, 39 + applicationServerKey: config.public.subscriptionsPublicKey, 39 40 }); 40 41 41 42 const fetch = useRequestFetch(); 42 43 43 - const response = await fetch(`/api/subscription/`, { 44 + await fetch(`/api/subscription/`, { 44 45 method: "POST", 45 46 body: sub.toJSON(), 46 47 ...useFetchOptions(), ··· 49 50 console.warn(err); 50 51 // await this.fetch(); 51 52 }); 52 - 53 - console.log(response); 54 53 } 55 54 </script> 56 55 ··· 93 92 <div> 94 93 <h2 class="text-muted-text text-lg">{{ t("push") }}</h2> 95 94 <button 95 + type="button" 96 96 data-testid="add-label-button" 97 97 class="bg-surface border-secondary h-8 cursor-pointer rounded border px-2 text-white" 98 98 @click="sendPush()" 99 99 > 100 - {{ t("push") }} 100 + {{ t("push") }} 101 101 </button> 102 102 </div> 103 103 </div>
+2
nuxt.config.ts
··· 39 39 runtimeConfig: { 40 40 public: { 41 41 baseURL: process.env.BASE_URL ?? "", 42 + subscriptionsPublicKey: 43 + process.env.NUXT_PUBLIC_SUBSCRIPTIONS_PUBLIC_KEY ?? "", 42 44 }, 43 45 upstash: { 44 46 url: process.env.UPSTASH_URL,
-7
server/api/subscription/.get.ts
··· 1 - import type { PushSubscriptionJSON } from "~~/shared/types"; 2 - 3 - export default defineAuthenticatedEventHandler( 4 - async (_event, userId): Promise<PushSubscriptionJSON[]> => { 5 - return await Subscriptions.getAll(userId); 6 - }, 7 - );
-3
server/api/subscription/sendNotification.post.ts
··· 1 - export default defineAuthenticatedEventHandler(async (_event, userId) => { 2 - await Subscriptions.sendNotification(userId); 3 - });
+1 -1
server/tasks/notify/push.ts
··· 1 1 export default defineTask({ 2 2 async run(_event) { 3 - await Todos.sendMsg() 3 + await Todos.sendMsg(); 4 4 5 5 const result = { 6 6 sent: 10,
+1
server/types/web-push.d.ts
··· 1 + declare module "web-push";
+2 -3
server/utils/authHandle.ts
··· 37 37 return createError({ 38 38 statusCode: 400, 39 39 statusMessage: "No Session set", 40 - }); 41 - 40 + }); 42 41 } 43 42 44 43 return result.session.userId; 45 44 } 46 - }, 45 + }, 47 46 48 47 async getUserIdOrThrow(event: H3Event): Promise<string> { 49 48 const session = await AuthApi.getUserId(event);
+1 -1
server/utils/db/subscription.ts
··· 8 8 9 9 webpush.setVapidDetails( 10 10 "mailto:you@example.com", 11 - process.env.SUBSCRIPTIONS_PUBLIC_KEY, 11 + process.env.NUXT_PUBLIC_SUBSCRIPTIONS_PUBLIC_KEY, 12 12 process.env.SUBSCRIPTIONS_PRIVATE_KEY, 13 13 ); 14 14
+32 -28
server/utils/db/todos.ts
··· 1 1 import type { NuxtError } from "nuxt/app"; 2 2 import { updateOrInsertAfterTodo } from "~~/shared/array"; 3 3 import { toLocalDateString } from "~~/shared/date"; 4 - import { Time, type Todo, type UUID } from "~~/shared/types"; 4 + import type { Todo, UUID } from "~~/shared/types"; 5 5 6 6 function getKey(userId: string): string { 7 7 return `todos:${userId}`; ··· 11 11 async getAll(userId: string): Promise<Todo[]> { 12 12 const storage = useStorage(); 13 13 return (await storage.get<Todo[]>(getKey(userId))) ?? []; 14 - }, 14 + }, 15 15 16 - async sendMsg(): Promise<void> { 17 - const keys = await useStorage().getKeys(); 18 - const filteredKeys = keys.filter((key) => key.startsWith("todos:")); 16 + async sendMsg(): Promise<void> { 17 + const keys = await useStorage().getKeys(); 18 + const filteredKeys = keys.filter((key) => key.startsWith("todos:")); 19 19 20 - filteredKeys.forEach(async (key) => { 21 - const todos = await useStorage().get<Todo[]>(key); 20 + filteredKeys.forEach(async (key) => { 21 + const todos = await useStorage().get<Todo[]>(key); 22 22 23 - todos?.forEach(todo => { 24 - if (todo.time?.type === "point") { 25 - const date = new Date(todo.time.time); 26 - const now = new Date(); 23 + todos?.forEach((todo) => { 24 + if (todo.time?.type === "point") { 25 + const date = new Date(todo.time.time); 26 + const now = new Date(); 27 27 28 - const sameMinute = 29 - date.getUTCFullYear() === now.getUTCFullYear() && 30 - date.getUTCMonth() === now.getUTCMonth() && 31 - date.getUTCDate() === now.getUTCDate() && 32 - date.getUTCHours() === now.getUTCHours() && 33 - date.getUTCMinutes() === now.getUTCMinutes(); 28 + const sameMinute = 29 + date.getUTCFullYear() === now.getUTCFullYear() && 30 + date.getUTCMonth() === now.getUTCMonth() && 31 + date.getUTCDate() === now.getUTCDate() && 32 + date.getUTCHours() === now.getUTCHours() && 33 + date.getUTCMinutes() === now.getUTCMinutes(); 34 34 35 - if (sameMinute) { 36 - Subscriptions.sendNotification(key.substring(6), todo.title, todo.note); 37 - } 38 - } 39 - }) 40 - }) 41 - }, 35 + if (sameMinute) { 36 + Subscriptions.sendNotification( 37 + key.substring(6), 38 + todo.title, 39 + todo.note, 40 + ); 41 + } 42 + } 43 + }); 44 + }); 45 + }, 42 46 43 - async getAllWithTimeStamp(userId: string): Promise<Todo[]> { 44 - let todos = await Todos.getAll(userId); 47 + async getAllWithTimeStamp(userId: string): Promise<Todo[]> { 48 + const todos = await Todos.getAll(userId); 45 49 46 - return todos.filter(todo => todo.time?.type === "range"); 47 - }, 50 + return todos.filter((todo) => todo.time?.type === "range"); 51 + }, 48 52 49 53 async move( 50 54 userId: string,