Skip to content

Commit ea2cc22

Browse files
Add Zendesk Link (#873)
* Adjust ContactView to redirect, and also align it with ReportBugView. * Update emails to always include the contact form link * Fix lint * Swap for an external redirect component. * 🌐 Language update --------- Co-authored-by: Andreas Müller <mail@devmount.de>
1 parent a4b310a commit ea2cc22

File tree

11 files changed

+86
-133
lines changed

11 files changed

+86
-133
lines changed

backend/src/appointment/controller/mailer.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def __init__(
5151
self,
5252
to: str,
5353
sender: str = os.getenv('SERVICE_EMAIL'),
54-
reply_to: str = os.getenv('SUPPORT_EMAIL'),
54+
reply_to: str = None,
5555
subject: str = '',
5656
html: str = '',
5757
plain: str = '',
@@ -100,7 +100,8 @@ def build(self):
100100
message['Subject'] = self.subject
101101
message['From'] = self.sender
102102
message['To'] = self.to
103-
message['Reply-To'] = self.reply_to
103+
if self.reply_to:
104+
message['Reply-To'] = self.reply_to
104105

105106
# add body as html and text parts
106107
message.set_content(self.text())

backend/src/appointment/templates/email/includes/base.jinja2

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,14 @@
4949
{% block call_to_action %}{% endblock %}
5050
</div>
5151
{% endif %}
52-
{% if show_contact_form_support_hint %}
5352
<div style="text-align: center; margin-left: auto; margin-right: auto; margin-bottom: 24px; padding: 12px; max-width: 310px;">
54-
{{ l10n('mail-brand-support-hint', lang=lang if lang else None)|safe }}
55-
</div>
56-
{% endif %}
5753
{% if show_contact_form_reply_hint %}
58-
{% set link = '<a href="%(url)s">%(label)s</a>'|format(url=homepage_url + '/contact', label=l10n('mail-brand-contact-form', lang=lang if lang else None)) %}
59-
<div style="text-align: center; margin-left: auto; margin-right: auto; margin-bottom: 24px; padding: 12px; max-width: 310px;">
6054
{{ l10n('mail-brand-reply-hint-attendee-info', { 'name': name }, lang if lang else None)|safe }}
6155
<br/><br/>
56+
{% endif %}
57+
{% set link = '<a href="%(url)s">%(label)s</a>'|format(url=homepage_url + '/contact', label=l10n('mail-brand-contact-form', lang=lang if lang else None)) %}
6258
{{ l10n('mail-brand-reply-hint', {'contact_form_link': link}, lang if lang else None)|safe }}
6359
</div>
64-
{% endif %}
6560
{% include './includes/footer.jinja2' %}
6661
</body>
6762
</html>

frontend/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,7 @@ VITE_REPORT_BUG_URL=https://github.com/thunderbird/appointment/issues/new?assign
3131

3232
# If the browser's hour setting is empty or missing fallback to this value
3333
VITE_DEFAULT_HOUR_FORMAT=12
34+
35+
# You could use a url or a mailto: email address.
36+
VITE_SUPPORT_URL=
37+

frontend/.env.prod.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ VITE_REPORT_BUG_URL=https://github.com/thunderbird/appointment/issues/new?assign
2222

2323
# If the browser's hour setting is empty or missing fallback to this value
2424
VITE_DEFAULT_HOUR_FORMAT=12
25+
26+
VITE_SUPPORT_URL=https://tbpro.zendesk.com/hc/requests/new

frontend/.env.stage.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ VITE_REPORT_BUG_URL=https://github.com/thunderbird/appointment/issues/new?assign
2222

2323
# If the browser's hour setting is empty or missing fallback to this value
2424
VITE_DEFAULT_HOUR_FORMAT=12
25+
26+
VITE_SUPPORT_URL=https://tbpro.zendesk.com/hc/requests/new
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<script setup lang="ts">
2+
import { onMounted } from 'vue';
3+
import LoadingSpinner from '@/elements/LoadingSpinner.vue';
4+
5+
// component properties
6+
interface Props {
7+
redirectUrl: string
8+
}
9+
10+
const props = withDefaults(defineProps<Props>(), {
11+
redirectUrl: '/',
12+
});
13+
14+
const redirect = async () => {
15+
window.location.replace(props.redirectUrl);
16+
};
17+
18+
onMounted(() => {
19+
redirect();
20+
});
21+
</script>
22+
<template>
23+
<div class="full-page">
24+
<h2>
25+
<i18n-t keypath="text.redirectedNotice">
26+
<template v-slot:url>
27+
<a class="underline underline-offset-2"
28+
:href="redirectUrl">
29+
{{ redirectUrl }}
30+
</a>
31+
</template>
32+
</i18n-t>
33+
</h2>
34+
<loading-spinner/>
35+
</div>
36+
</template>
37+
<style scoped>
38+
@import '@/assets/styles/mixins.pcss';
39+
40+
.full-page {
41+
@mixin faded-background var(--colour-neutral-border);
42+
43+
position: fixed;
44+
display: flex;
45+
flex-direction: column;
46+
gap: 2rem;
47+
justify-content: center;
48+
align-items: center;
49+
top: 0;
50+
left: 0;
51+
width: 100vw;
52+
height: 100vh;
53+
z-index: 50;
54+
}
55+
</style>

frontend/src/components/NavBar.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ const isNavEntryActive = (item: string) => {
9090
<router-link :to="{ name: 'report-bug' }" class="flex items-center justify-between gap-1 p-2" data-testid="user-nav-report-bug-menu">
9191
{{ t('navBar.reportBug') }} <icon-external-link class="size-4"/>
9292
</router-link>
93-
<router-link :to="{ name: 'contact' }" class="p-2" data-testid="user-nav-contact-menu">
94-
{{ t('label.contact') }}
93+
<router-link :to="{ name: 'contact' }" class="flex items-center justify-between gap-1 p-2" data-testid="user-nav-contact-menu">
94+
{{ t('label.contact') }} <icon-external-link class="size-4"/>
9595
</router-link>
9696
<hr class="border-teal-500" />
9797
<router-link :to="{ name: 'logout' }" class="p-2" data-testid="user-nav-logout-menu">

frontend/src/locales/de.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,13 +357,13 @@
357357
"title": "Log dich ein oder erstelle ein Benutzerkonto"
358358
},
359359
"remoteError": {
360-
"email-mismatch": "Die E-Mail, mit der Du dich angemeldet hast, muss mit Deinem Mozilla-Konto übereinstimmen.",
361-
"invite-not-valid": "Der eingegebene Einladungscode ist ungültig.",
362-
"unknown-error": "Ein unbekannter Fehler ist aufgetreten.",
363360
"disabled-account": "Dieses Konto ist deaktiviert.",
361+
"email-mismatch": "Die E-Mail, mit der Du dich angemeldet hast, muss mit Deinem Mozilla-Konto übereinstimmen.",
362+
"email-not-in-session": "Es gab ein Problem mit der Anmeldesitzung. Bitte versuch es erneut.",
364363
"invalid-credentials": "Es gab ein Problem mit Deinen Anmeldedaten.",
365364
"invalid-state": "Die Anmeldesitzung ist abgelaufen. Bitte versuch es erneut.",
366-
"email-not-in-session": "Es gab ein Problem mit der Anmeldesitzung. Bitte versuch es erneut."
365+
"invite-not-valid": "Der eingegebene Einladungscode ist ungültig.",
366+
"unknown-error": "Ein unbekannter Fehler ist aufgetreten."
367367
},
368368
"signUp": {
369369
"title": "Du hast einen Einladungscode? Gib ihn hier ein"
@@ -448,6 +448,7 @@
448448
"ownerNeedsToConfirmBooking": "Wenn diese Option aktiviert ist, müssen Buchungen dieses Zeitplans bestätigt oder ablehnt werden. Wenn diese Option deaktiviert ist, werden alle Zeiten automatisch bestätigt.",
449449
"preferredEmailHelp": "Die E-Mail-Adresse festlegen, die für ausgehende Kommunikation in Kalenderereignissen verwendet werden soll.",
450450
"recipientsCanScheduleBetween": "Empfänger können einen Termin zwischen {earliest} und {farthest} ab dem aktuellen Zeitpunkt wählen. ",
451+
"redirectedNotice": "Du wirst weitergeleitet nach {url}.",
451452
"refreshLinkNotice": "Dadurch wird dein Link erneuert. Deine alten Links werden nicht länger funktionieren.",
452453
"requestInformationSentToOwner": "Der Kalenderbesitzer wurde per E-Mail über deine Buchungsanfrage informiert.",
453454
"scheduleSettings": {

frontend/src/locales/en.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,13 +357,13 @@
357357
"title": "Sign in or create an account"
358358
},
359359
"remoteError": {
360-
"email-mismatch": "The email you've signed up and logged in with must be the same as your Mozilla Account.",
361-
"invite-not-valid": "The invite code you've entered is not valid.",
362-
"unknown-error": "An unknown error occurred.",
363360
"disabled-account": "This account is disabled.",
361+
"email-mismatch": "The email you've signed up and logged in with must be the same as your Mozilla Account.",
362+
"email-not-in-session": "There was a problem with the login session. Please try again.",
364363
"invalid-credentials": "There was a problem with your credentials.",
365364
"invalid-state": "The login session has expired. Please try again.",
366-
"email-not-in-session": "There was a problem with the login session. Please try again."
365+
"invite-not-valid": "The invite code you've entered is not valid.",
366+
"unknown-error": "An unknown error occurred."
367367
},
368368
"signUp": {
369369
"title": "Have an invite code? Enter it here"
@@ -448,6 +448,7 @@
448448
"ownerNeedsToConfirmBooking": "When this option is active, you will be required to confirm or decline times booked on your schedule. When this option is deactivated, all times will automatically be confirmed.",
449449
"preferredEmailHelp": "Set the email you'll use for out-going communication. This will be used in calendar events and emails.",
450450
"recipientsCanScheduleBetween": "Recipients can schedule a {duration} appointment between {earliest} and {farthest} ahead of time.",
451+
"redirectedNotice": "You are being redirected to {url}.",
451452
"refreshLinkNotice": "This refreshes your link. Your old links will no longer work.",
452453
"requestInformationSentToOwner": "An information about this booking request has been emailed to the owner.",
453454
"scheduleSettings": {

frontend/src/views/ContactView.vue

Lines changed: 3 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,9 @@
11
<script setup lang="ts">
2-
import { inject, ref } from 'vue';
3-
import { useI18n } from 'vue-i18n';
4-
import { useUserStore } from '@/stores/user-store';
5-
import { AlertSchemes } from '@/definitions';
6-
import { callKey } from '@/keys';
7-
import { BooleanResponse } from '@/models';
8-
import PrimaryButton from '@/elements/PrimaryButton.vue';
9-
import AlertBox from '@/elements/AlertBox.vue';
10-
import TextInput from '@/elements/TextInput.vue';
2+
import ExternalRedirect from '@/components/ExternalRedirect.vue';
113
12-
// icons
13-
import { IconSend } from '@tabler/icons-vue';
4+
const redirectUrl = import.meta.env?.VITE_SUPPORT_URL;
145
15-
// component constants
16-
const user = useUserStore();
17-
const { t } = useI18n();
18-
const call = inject(callKey);
19-
20-
// form data
21-
const form = ref<HTMLFormElement>(null);
22-
const topic = ref('');
23-
const details = ref('');
24-
const sendingState = ref(0);
25-
26-
// empty all form inputs
27-
const resetForm = () => {
28-
topic.value = '';
29-
details.value = '';
30-
};
31-
32-
// send support request
33-
const send = async () => {
34-
if (!form.value.checkValidity()) {
35-
form.value.reportValidity();
36-
return;
37-
}
38-
const postObj = { topic: topic.value, details: details.value };
39-
const { error, data }: BooleanResponse = await call('support').post(postObj).json();
40-
if (!error.value && data.value) {
41-
sendingState.value = AlertSchemes.Success;
42-
resetForm();
43-
} else {
44-
sendingState.value = AlertSchemes.Error;
45-
}
46-
};
476
</script>
48-
497
<template>
50-
<!-- page title area -->
51-
<div v-if="user.authenticated" class="flex flex-col items-center justify-center gap-4">
52-
<div class="text-4xl font-light">{{ t('heading.contactRequest') }}</div>
53-
<div class="w-full max-w-lg">{{ t('text.contactRequestForm') }}</div>
54-
<alert-box
55-
v-if="sendingState === AlertSchemes.Success"
56-
:title="t('label.success')"
57-
@close="sendingState = 0"
58-
:scheme="AlertSchemes.Success"
59-
>
60-
{{ t('info.messageWasSent') }}
61-
</alert-box>
62-
<alert-box
63-
v-if="sendingState === AlertSchemes.Error"
64-
:title="t('label.error')"
65-
@close="sendingState = 0"
66-
:scheme="AlertSchemes.Error"
67-
>
68-
{{ t('info.messageWasNotSent') }}
69-
</alert-box>
70-
<form class="flex w-full max-w-lg flex-col gap-2" ref="form">
71-
<label class="flex flex-col gap-1">
72-
<div class="font-medium text-gray-500 dark:text-gray-300">
73-
{{ t("label.topic") }}
74-
</div>
75-
<input type="text" v-model="topic" class="w-full rounded-md" required />
76-
</label>
77-
<label class="flex flex-col gap-1">
78-
<div class="font-medium text-gray-500 dark:text-gray-300">
79-
{{ t("label.message") }}
80-
</div>
81-
<text-input
82-
v-model="details"
83-
:placeholder="t('placeholder.writeHere')"
84-
:maxlength="2500"
85-
/>
86-
</label>
87-
</form>
88-
<primary-button class="btn-send" @click="send" :title="t('label.send')">
89-
<icon-send />
90-
{{ t('label.send') }}
91-
</primary-button>
92-
</div>
8+
<external-redirect :redirect-url="redirectUrl"/>
939
</template>

0 commit comments

Comments
 (0)