Skip to content

Commit ea6e080

Browse files
takeruhukushimanicobao
authored andcommitted
feat(post): Add QR code dialog for post sharing
- Adds a new 'ShareDialog.vue' component to display the post URL as a QR code and provide a "copy link" button. - Modifies 'PostDetails.vue' to call this dialog from the share button. - The previous Web Share API logic is preserved as a comment for potential future use as a fallback mechanism. Signed-off-by: takeru.fukushima <100330935+takeruhukushima@users.noreply.github.com>
1 parent 7b9b01c commit ea6e080

4 files changed

Lines changed: 167 additions & 4 deletions

File tree

services/agora/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"@quasar/app-vite": "2.3.0",
7676
"@sentry/vite-plugin": "^4.3.0",
7777
"@types/node": "^24.5.2",
78+
"@types/qrcode": "^1.5.5",
7879
"@types/sanitize-html": "^2.13.0",
7980
"@vitejs/plugin-basic-ssl": "^2.0.0",
8081
"@vue/eslint-config-prettier": "^10.2.0",

services/agora/src/components/post/PostDetails.vue

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ import PostActionBar from "./interactionBar/PostActionBar.vue";
8383
import FloatingBottomContainer from "../navigation/FloatingBottomContainer.vue";
8484
import CommentComposer from "./comments/CommentComposer.vue";
8585
import { ref, computed, watch, onMounted } from "vue";
86-
import { useWebShare } from "src/utils/share/WebShare";
86+
// import { useWebShare } from "src/utils/share/WebShare";
87+
import { useQuasar } from "quasar";
88+
import ShareDialog from "./ShareDialog.vue";
8789
import { useConversationUrl } from "src/utils/url/conversationUrl";
8890
import ZKHoverEffect from "../ui-library/ZKHoverEffect.vue";
8991
import type { ExtendedConversation } from "src/shared/types/zod";
@@ -108,7 +110,8 @@ const analysisPageRef = ref<InstanceType<typeof AnalysisPage>>();
108110
109111
const opinionCountOffset = ref(0);
110112
111-
const webShare = useWebShare();
113+
// const webShare = useWebShare();
114+
const $q = useQuasar();
112115
const { getConversationUrl } = useConversationUrl();
113116
const { invalidateAnalysis, forceRefreshAnalysis } =
114117
useInvalidateCommentQueries();
@@ -200,14 +203,28 @@ async function submittedComment(opinionSlugId: string): Promise<void> {
200203
forceRefreshAnalysis(props.conversationData.metadata.conversationSlugId);
201204
}
202205
203-
async function shareClicked(): Promise<void> {
206+
function shareClicked(): void {
204207
const sharePostUrl = getConversationUrl(
205208
props.conversationData.metadata.conversationSlugId
206209
);
210+
const shareTitle = "Agora - " + props.conversationData.payload.title;
211+
212+
// Always open the QR code dialog
213+
$q.dialog({
214+
component: ShareDialog,
215+
componentProps: {
216+
url: sharePostUrl,
217+
title: shareTitle,
218+
},
219+
});
220+
221+
/*
222+
// Original Web Share logic is kept here, commented out.
207223
await webShare.share(
208-
"Agora - " + props.conversationData.payload.title,
224+
shareTitle,
209225
sharePostUrl
210226
);
227+
*/
211228
}
212229
213230
onMounted(() => {
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<template>
2+
<q-dialog ref="dialogRef" @hide="onDialogHide">
3+
<q-card class="share-dialog-card">
4+
<q-btn
5+
icon="close"
6+
flat
7+
round
8+
dense
9+
class="close-button"
10+
@click="onDialogCancel"
11+
/>
12+
13+
<div class="dialog-content">
14+
<div class="qr-code-container">
15+
<img v-if="qrCodeDataUrl" :src="qrCodeDataUrl" alt="QR Code" />
16+
</div>
17+
18+
<q-btn
19+
no-caps
20+
class="copy-button"
21+
@click="copyUrl"
22+
>
23+
<div class="flex items-center no-wrap">
24+
<div>Copy link to conversation</div>
25+
</div>
26+
</q-btn>
27+
</div>
28+
</q-card>
29+
</q-dialog>
30+
</template>
31+
32+
<script setup lang="ts">
33+
import { useDialogPluginComponent, useQuasar, copyToClipboard } from "quasar";
34+
import QRCode from "qrcode";
35+
import { ref, onMounted } from "vue";
36+
37+
const props = defineProps<{
38+
url: string;
39+
title: string;
40+
}>();
41+
42+
defineEmits([...useDialogPluginComponent.emits]);
43+
44+
const { dialogRef, onDialogHide, onDialogCancel } = useDialogPluginComponent();
45+
const $q = useQuasar();
46+
47+
const qrCodeDataUrl = ref("");
48+
49+
onMounted(async () => {
50+
try {
51+
qrCodeDataUrl.value = await QRCode.toDataURL(props.url, {
52+
width: 218,
53+
margin: 1,
54+
color: {
55+
dark: "#000000",
56+
light: "#0000", // Transparent background
57+
},
58+
});
59+
} catch (err) {
60+
console.error(err);
61+
}
62+
});
63+
64+
function copyUrl() {
65+
copyToClipboard(props.url)
66+
.then(() => {
67+
$q.notify({
68+
message: "Copied to clipboard!",
69+
color: "positive",
70+
position: "top",
71+
icon: "check",
72+
});
73+
})
74+
.catch(() => {
75+
$q.notify({
76+
message: "Could not copy to clipboard.",
77+
color: "negative",
78+
position: "top",
79+
icon: "warning",
80+
});
81+
});
82+
}
83+
</script>
84+
85+
<style scoped lang="scss">
86+
.share-dialog-card {
87+
width: 428px;
88+
height: 450px;
89+
border-radius: 32px;
90+
background: linear-gradient(114.81deg, #f1eeff 46.45%, #e8f1ff 100.1%);
91+
padding: 24px;
92+
position: relative;
93+
display: flex;
94+
flex-direction: column;
95+
align-items: center;
96+
}
97+
98+
.close-button {
99+
position: absolute;
100+
top: 16px;
101+
right: 16px;
102+
width: 48px;
103+
height: 48px;
104+
border-radius: 16px;
105+
background-color: rgba(0, 0, 0, 0.05);
106+
}
107+
108+
.dialog-content {
109+
display: flex;
110+
flex-direction: column;
111+
align-items: center;
112+
gap: 16px;
113+
width: 100%;
114+
flex-grow: 1; /* Allow container to grow */
115+
justify-content: center; /* Center content vertically */
116+
}
117+
118+
119+
.qr-code-container {
120+
width: 218px;
121+
height: 218px;
122+
display: flex;
123+
align-items: center;
124+
justify-content: center;
125+
}
126+
127+
.copy-button {
128+
width: 241px;
129+
height: 48px;
130+
border-radius: 16px;
131+
background: linear-gradient(180deg, #6b4eff 30.96%, #4f92f6 99.99%);
132+
color: white;
133+
font-family: "Albert Sans", sans-serif;
134+
font-weight: 500;
135+
font-size: 16px; // Adjusted to fit better
136+
line-height: 24px;
137+
}
138+
</style>

services/agora/yarn.lock

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,13 @@
15021502
dependencies:
15031503
undici-types "~7.12.0"
15041504

1505+
"@types/qrcode@^1.5.5":
1506+
version "1.5.5"
1507+
resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.5.tgz#993ff7c6b584277eee7aac0a20861eab682f9dac"
1508+
integrity sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==
1509+
dependencies:
1510+
"@types/node" "*"
1511+
15051512
"@types/qs@*":
15061513
version "6.9.18"
15071514
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.18.tgz#877292caa91f7c1b213032b34626505b746624c2"

0 commit comments

Comments
 (0)