Skip to content

Commit

Permalink
v0.1.2
Browse files Browse the repository at this point in the history
commit 0842c17
Author: BackRunner <dev@backrunner.top>
Date:   Sun Aug 25 21:57:51 2024 +0800

    fix: do not send heartbeat to closed controller

commit 84ce79d
Author: BackRunner <dev@backrunner.top>
Date:   Sun Aug 25 21:56:18 2024 +0800

    fix: cannot remove expired subscription

commit 4db710a
Author: BackRunner <dev@backrunner.top>
Date:   Sun Aug 25 20:35:54 2024 +0800

    feat: enhance the experience of the alert and the guidance

commit 4cfc1dd
Author: BackRunner <dev@backrunner.top>
Date:   Sun Aug 25 20:26:00 2024 +0800

    feat: add privacy policy page

commit 9920156
Merge: e937f8c 19e8a5b
Author: BackRunner <dev@backrunner.top>
Date:   Sun Aug 25 18:50:36 2024 +0800

    Merge branch 'main' into dev

commit e937f8c
Author: BackRunner <dev@backrunner.top>
Date:   Sun Aug 25 18:48:09 2024 +0800

    chore: update roadmap

commit 7a7cd9f
Author: BackRunner <dev@backrunner.top>
Date:   Sun Aug 25 18:45:16 2024 +0800

    feat: allow to set an allowed email list

commit eeb575b
Author: BackRunner <dev@backrunner.top>
Date:   Sun Aug 25 18:27:09 2024 +0800

    chore: update roadmap

commit 2581911
Author: BackRunner <dev@backrunner.top>
Date:   Sun Aug 25 18:26:33 2024 +0800

    chore: update roadmap

commit a64c901
Author: BackRunner <dev@backrunner.top>
Date:   Sun Aug 25 18:22:38 2024 +0800

    fix: align the encoding method

commit e639829
Author: BackRunner <dev@backrunner.top>
Date:   Sun Aug 25 18:19:06 2024 +0800

    feat: allow to push notification with custom icon & add highlight effect to the card

chore: bump version
  • Loading branch information
backrunner committed Aug 25, 2024
1 parent 19e8a5b commit 7e183ac
Show file tree
Hide file tree
Showing 16 changed files with 250 additions and 27 deletions.
6 changes: 6 additions & 0 deletions Roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Planned Features

### v0.1.2

- [x] Add a privacy policy page.
- [x] Add a cookie agreement banner at the bottom of the page.
- [x] Add an option to allow an alert will not show up in the future once user dismiss it.

### v0.2.0

- [ ] Allow to delete a notification in both desktop and mobile devices.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "alphapush",
"type": "module",
"version": "0.1.1",
"version": "0.1.2",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
Expand Down
12 changes: 10 additions & 2 deletions src/components/alert/EnhanceExperienceAlert.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<template>
<Alert class="mb-4" variant="warning" :closable="true" v-if="shouldShow">
<Alert
class="mb-4"
variant="warning"
:closable="true"
v-if="shouldShow"
id="enhance-experience"
:allowDismissForever="true"
>
<AlertTitle class="mb-2 font-bold">Enhance Your Experience</AlertTitle>
<AlertDescription>{{ pwaInstallTip }}</AlertDescription>
</Alert>
Expand All @@ -25,7 +32,8 @@ const pwaInstallTip = computed(() =>
);
function checkPwaInstallation() {
shouldShow.value = !window.matchMedia('(display-mode: standalone)').matches;
const isDismissed = localStorage.getItem('alert_enhance-experience_dismissed') === 'true';
shouldShow.value = !window.matchMedia('(display-mode: standalone)').matches && !isDismissed;
}
onMounted(() => {
Expand Down
46 changes: 46 additions & 0 deletions src/components/common/CookiePrivacyBanner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { Button } from '@/components/ui/button';
const isVisible = ref(false);
onMounted(() => {
const hasAcceptedCookies = localStorage.getItem('cookiesAccepted');
if (!hasAcceptedCookies) {
setTimeout(() => {
isVisible.value = true;
}, 500);
}
});
const acceptCookies = () => {
localStorage.setItem('cookiesAccepted', 'true');
isVisible.value = false;
};
</script>

<template>
<Transition name="slide-up">
<div v-if="isVisible" class="fixed bottom-0 left-0 right-0 bg-background border-t border-border p-4 shadow-lg z-50">
<div class="container mx-auto flex flex-col sm:flex-row items-center justify-between">
<p class="text-sm text-muted-foreground mb-4 sm:mb-0 sm:mr-4">
We use cookies and device information to improve your experience. By continuing to use our site, you agree to
our
<a href="/privacy-policy" class="text-primary hover:underline">Privacy Policy</a>.
</p>
<Button @click="acceptCookies" variant="outline" size="sm"> Accept </Button>
</div>
</div>
</Transition>
</template>

<style scoped>
.slide-up-enter-active,
.slide-up-leave-active {
transition: transform 0.3s ease-out;
}
.slide-up-enter-from,
.slide-up-leave-to {
transform: translateY(100%);
}
</style>
16 changes: 15 additions & 1 deletion src/components/common/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const isAllowedUser = session?.user?.email && allowedEmails.includes(session.use

<header class="bg-background border rounded-lg shadow-sm mt-4 mb-4">
<div class="container mx-auto px-6 py-3 flex items-center justify-between">
<div class="text font-semibold text-primary">AlphaPush</div>
<div class="text font-semibold text-primary cursor-pointer" id="logo">AlphaPush</div>
{
isAllowedUser ? (
<TopUser
Expand All @@ -36,6 +36,20 @@ const isAllowedUser = session?.user?.email && allowedEmails.includes(session.use
</div>
</header>

<script>
document.addEventListener('DOMContentLoaded', () => {
const logo = document.getElementById('logo');
if (logo) {
logo.addEventListener('click', () => {
const currentPath = window.location.pathname;
if (currentPath !== '/' && currentPath !== '/403') {
window.location.href = '/';
}
});
}
});
</script>

<script>
document.addEventListener('DOMContentLoaded', () => {
const topUser = document.querySelector('top-user');
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/Page.astro
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const session = await getSession(Astro.request);
const allowedEmails = import.meta.env.ALLOWED_EMAILS?.split(',').map((email: string) => email.trim()) || [];
const isAllowedUser = session?.user?.email && allowedEmails.includes(session.user.email);
const isLoggedIn = !!session?.user && isAllowedUser;
const userEmail = isAllowedUser ? (session?.user?.email || '') : '';
const userEmail = isAllowedUser ? session?.user?.email || '' : '';
// Get the user agent from the request headers
const userAgent = Astro.request.headers.get('user-agent') || '';
Expand Down Expand Up @@ -330,4 +330,4 @@ const isMobile = isMobileDevice(userAgent);
});
</script>
</body>
</html>
</html>
10 changes: 7 additions & 3 deletions src/components/mobile/PwaGuidance.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ onMounted(() => {
isIOS.value = /iPad|iPhone|iPod/.test(navigator.userAgent);
isMobile.value = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
// Check if already installed as PWA and if it's a mobile device
// Check if already installed as PWA, if it's a mobile device, and if guidance hasn't been shown before
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
if (!isStandalone && isMobile.value) {
const hasSeenGuidance = localStorage.getItem('hasSeenPWAGuidance');
if (!isStandalone && isMobile.value && !hasSeenGuidance) {
isOpen.value = true;
// Mark that the user has seen the guidance
localStorage.setItem('hasSeenPWAGuidance', 'true');
}
});
Expand Down Expand Up @@ -74,4 +78,4 @@ const closeDrawer = () => {
</DrawerFooter>
</DrawerContent>
</Drawer>
</template>
</template>
36 changes: 34 additions & 2 deletions src/components/ui/alert/Alert.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed, provide } from 'vue';
import { ref, computed, provide, onMounted } from 'vue';
import type { HTMLAttributes } from 'vue';
import { type AlertVariants, alertVariants } from '.';
import { cn } from '@/lib/utils';
Expand All @@ -8,22 +8,54 @@ const props = defineProps<{
class?: HTMLAttributes['class'];
variant?: AlertVariants['variant'];
closable?: boolean;
allowDismissForever?: boolean;
id?: string;
}>();
const isVisible = ref(true);
const showDismissForever = ref(false);
const closeAlert = () => {
if (props.allowDismissForever) {
showDismissForever.value = true;
} else {
isVisible.value = false;
}
};
const dismissForever = () => {
if (props.allowDismissForever && props.id) {
isVisible.value = false;
localStorage.setItem(`alert_${props.id}_dismissed`, 'true');
}
};
const dismissTemporarily = () => {
isVisible.value = false;
};
const closable = computed(() => props.closable);
const allowDismissForever = computed(() => props.allowDismissForever);
provide('closable', closable);
provide('closeAlert', closeAlert);
provide('showDismissForever', showDismissForever);
provide('dismissForever', dismissForever);
provide('dismissTemporarily', dismissTemporarily);
provide('allowDismissForever', allowDismissForever);
onMounted(() => {
if (props.allowDismissForever && props.id) {
const isDismissed = localStorage.getItem(`alert_${props.id}_dismissed`) === 'true';
if (isDismissed) {
isVisible.value = false;
}
}
});
</script>

<template>
<div v-if="isVisible" :class="cn(alertVariants({ variant: props.variant }), props.class)" role="alert">
<slot />
</div>
</template>
</template>
23 changes: 18 additions & 5 deletions src/components/ui/alert/AlertTitle.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { inject } from 'vue';
import type { ComputedRef, HTMLAttributes } from 'vue';
import { inject, ref } from 'vue';
import type { ComputedRef, HTMLAttributes, Ref } from 'vue';
import { Icon } from '@iconify/vue';
import { cn } from '@/lib/utils';
Expand All @@ -10,15 +10,28 @@ const props = defineProps<{
const closable = inject<ComputedRef<boolean>>('closable');
const closeAlert = inject('closeAlert', () => {});
const showDismissForever = inject<Ref<boolean>>('showDismissForever');
const dismissForever = inject('dismissForever', () => {});
const dismissTemporarily = inject('dismissTemporarily', () => {});
const allowDismissForever = inject<ComputedRef<boolean>>('allowDismissForever');
</script>

<template>
<div :class="cn('relative flex items-center justify-between', props.class)">
<h5 :class="cn(`${closable ? '' : 'mb-1 '}font-medium leading-none tracking-tight`)">
<slot />
</h5>
<button v-if="closable" @click="closeAlert">
<Icon icon="mdi:close" class="w-4 h-4" />
</button>
<div v-if="closable" class="flex items-center">
<template v-if="!showDismissForever || !allowDismissForever">
<button @click="closeAlert">
<Icon icon="mdi:close" class="w-4 h-4" />
</button>
</template>
<template v-else-if="allowDismissForever">
<span class="mr-2 text-sm font-semibold">Dismiss forever?</span>
<button @click="dismissForever" class="mr-2 text-sm font-medium">Yes</button>
<button @click="dismissTemporarily" class="text-sm font-medium">No</button>
</template>
</div>
</div>
</template>
2 changes: 1 addition & 1 deletion src/pages/403.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Card, CardContent } from '@/components/ui/card';
<Card>
<CardContent class="flex flex-col items-center justify-center">
<h1 class="text font-bold mb-4">Access Denied</h1>
<p class="text mb-2">Sorry, you don't have permission to access the push service.</p>
<p class="text-sm mb-2">Sorry, you don't have permission to access the push service.</p>
</CardContent>
</Card>
</Page>
2 changes: 1 addition & 1 deletion src/pages/api/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export const POST: APIRoute = async ({ request, locals }) => {
reason: (error as Error).message,
});

if (error instanceof Error && 'statusCode' in error && error.statusCode === 410) {
if (error instanceof Error && 'statusCode' in error && (error as any).statusCode === 410) {
subscriptionsToRemove.push(sub.id);
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/pages/api/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@ export const GET: APIRoute = async ({ request, locals }) => {
return;
}
try {
controller.enqueue(`event: heartbeat\ndata: ${new Date().toISOString()}\n\n`);
// Check if the controller is closed
if (controller.desiredSize !== null) {
controller.enqueue(`event: heartbeat\ndata: ${new Date().toISOString()}\n\n`);
} else {
throw new Error('Controller is closed');
}
} catch (error) {
console.error('Error sending heartbeat:', error);
clearInterval(heartbeatInterval);
Expand Down
11 changes: 7 additions & 4 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
---
import { getSession } from 'auth-astro/server';
import CookiePrivacyBanner from '@/components/common/CookiePrivacyBanner.vue';
import Page from '@/components/common/Page.astro';
import NotificationList from '@/components/notification/NotificationList.vue';
import { getSession } from 'auth-astro/server';
const session = await getSession(Astro.request);
let initialNotifications = [];
Expand All @@ -28,9 +30,9 @@ if (session?.user?.email) {
if (response.ok) {
const data = (await response.json()) as { notifications: any[]; totalPages: number; totalCount: number };
const highlightedNotificationId = Astro.url.searchParams.get('notificationId');
initialNotifications = data.notifications.map(notification => ({
initialNotifications = data.notifications.map((notification) => ({
...notification,
highlight: notification.id === highlightedNotificationId
highlight: notification.id === highlightedNotificationId,
}));
totalPages = data.totalPages;
} else {
Expand All @@ -46,4 +48,5 @@ if (session?.user?.email) {
initialNotifications={initialNotifications}
initialTotalPages={totalPages}
/>
</Page>
<CookiePrivacyBanner client:load />
</Page>
Loading

0 comments on commit 7e183ac

Please sign in to comment.