Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,14 @@ main {
min-height: 0;
margin-inline: 1rem;
}

&.public-route {
display: flex;
flex-direction: column;
flex-grow: 1;
min-height: 0;
padding-block-end: 1rem;
}
}

@media (--md) {
Expand All @@ -384,4 +392,4 @@ main {
margin-inline: 3.5rem;
}
}
</style>
</style>
2 changes: 2 additions & 0 deletions frontend/src/assets/styles/colours.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ html {
--colour-neutral-base: #FEFFFF;
--colour-neutral-lower: #F7F7F7;
--colour-neutral-lower-dark: #18181B; /* Forced dark mode colour */
--colour-neutral-lower-light: #F7F7F7; /* Forced light mode colour */
--colour-neutral-raised: var(--colour-neutral-base);
--colour-neutral-subtle: var(--colour-neutral-lower);
--colour-neutral-border: #E4E4E7;
Expand Down Expand Up @@ -76,6 +77,7 @@ html {
--colour-ti-black: #000000;
--colour-ti-base: #1A202C;
--colour-ti-base-dark: #EEEEF0; /* Forced dark mode colour */
--colour-ti-base-light: #1A202C;
--colour-ti-secondary: #4C4D58;
--colour-ti-muted: #737584;
--colour-ti-highlight: #1373D9;
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/assets/svg/appointment_calendar_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 8 additions & 4 deletions frontend/src/locales/de.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"app": {
"summary": "Lass Freunde und Kollegen Zeiten in deinem Kalender wählen. Terminabsprachen so simpel wie möglich.",
"tagline": "Weniger planen, mehr schaffen",
"title": "Thunderbird Appointment"
},
"tagline": "Weniger planen. Mehr schaffen.",
"title": "Thunderbird Appointment",
"description": "Mit Thunderbird Appointment findest Du ganz einfach einen Termin für ein Treffen. So kannst Du Verwaltungsaufwand hinter Dir lassen und Deinen Tag optimieren." },
"calDAVForm": {
"help": {
"location": "URL oder Hostname, den wir für die Verbindung deiner Kalender verwenden werden.",
Expand Down Expand Up @@ -296,7 +296,7 @@
"download": "Download",
"downloadICS": "Download ICS",
"downloadInvitation": "Termineinladung herunterladen",
"downloadTheIcsFile": "ICS-Datei herunterladen",
"downloadTheIcsFile": "Zu Kalender hinzufügen",
"downloadMyData": "Alle deine Daten von Appointment herunterladen",
"earliestBooking": "Früheste Buchung",
"edit": "Bearbeiten",
Expand Down Expand Up @@ -439,6 +439,7 @@
"startTime": "Startzeit",
"startUsingTba": "Starte mit TBA",
"status": "Status",
"subscribe": "Abonnieren",
"success": "Erfolg",
"sync": "Synchronisieren",
"syncCalendars": "Kalender synchronisieren",
Expand Down Expand Up @@ -618,6 +619,9 @@
"noCalendars": "Keine Kalenderkonten verbunden. Füge ein Konto hinzu, um zu beginnen."
}
},
"timeHasBeenConfirmed": "Der Termin wurde bestätigt. Thunderbird Appointment hat eine Kalendereinladung an {email} gesendet.",
"hostHasBeenNotified": "Der Gastgeber wurde benachrichtigt. Thunderbird Appointment wird dir eine E-Mail senden, sobald er antwortet.",
"virtualMeetingWith": "Online-Meeting mit {name}",
"timesAreDisplayedInLocalTimezone": "Die Zeiten werden in deiner lokalen Zeitzone {timezone} angezeigt.",
"titleIsReadyForBookings": "{title} ist für Buchungen bereit",
"updateLinkNotice": "Ein Ändern des Benutzernamens oder des Teillinks aktualisiert deinen Link. Alle alten Links werden dann nicht mehr funktionieren.",
Expand Down
11 changes: 8 additions & 3 deletions frontend/src/locales/en.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"app": {
"summary": "Invite others to grab times on your calendar. Choose a date. Make appointments as easy as it gets.",
"tagline": "Plan less, do more",
"title": "Thunderbird Appointment"
"tagline": "Plan less. Do more.",
"title": "Thunderbird Appointment",
"description": "Thunderbird Appointment makes it easy to find a time to meet. So you can ditch the admin and streamline your day."
},
"calDAVForm": {
"help": {
Expand Down Expand Up @@ -299,7 +300,7 @@
"download": "Download",
"downloadICS": "Download ICS",
"downloadInvitation": "Download invitation",
"downloadTheIcsFile": "Add to your calendar",
"downloadTheIcsFile": "Add to calendar",
"downloadMyData": "Download all your data from Appointment",
"earlier": "Earlier",
"earliestBooking": "Earliest Booking",
Expand Down Expand Up @@ -442,6 +443,7 @@
"startTime": "Start time",
"startUsingTba": "Try Appointment",
"status": "Status",
"subscribe": "Subscribe",
"success": "Success",
"sync": "Sync",
"syncCalendars": "Sync Calendars",
Expand Down Expand Up @@ -621,6 +623,9 @@
"noCalendars": "No calendar accounts connected. Add an account to get started."
}
},
"timeHasBeenConfirmed": "Your time has been confirmed. Thunderbird Appointment has sent a calendar invite to {email}.",
"hostHasBeenNotified": "Your host has been notified. Thunderbird Appointment will email you once they respond.",
"virtualMeetingWith": "Virtual meeting with {name}",
"timesAreDisplayedInLocalTimezone": "Times are displayed in your local timezone {timezone}.",
"titleIsReadyForBookings": "{title} is ready for bookings",
"updateLinkNotice": "Changing your username or slug will change your link. Your old link will no longer work.",
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ import {
} from '@/models';
import { BookingStatus } from './definitions';

/**
* Lowercases the first character of a string
*/
// Lowercases the first character of a string
export const lcFirst = (s: string): string => {
if (typeof s !== 'string' || !s) {
return '';
Expand Down Expand Up @@ -109,7 +107,7 @@ export const timeFormat = (): string => {
}

const format = Number(user.settings?.timeFormat ?? detected);
return format === 24 ? 'HH:mm' : 'hh:mm A';
return format === 24 ? 'HH:mm' : 'hh:mma';
};

// Check if we already have a local user preferred language
Expand Down
204 changes: 174 additions & 30 deletions frontend/src/views/BookerView/components/BookingViewSuccess.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,199 @@ import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user-store';

import ArtSuccessfulBooking from '@/elements/arts/ArtSuccessfulBooking.vue';
import PrimaryButton from '@/elements/PrimaryButton.vue';
import { dayjsKey } from '@/keys';
import { Appointment, Slot } from '@/models';
import { LinkButton, PrimaryButton } from '@thunderbirdops/services-ui';
import { apiUrlKey, dayjsKey } from '@/keys';
import { Appointment, Attendee, Slot } from '@/models';
import { PhArrowRight, PhDownloadSimple, PhConfetti } from '@phosphor-icons/vue';

const { t } = useI18n();
const router = useRouter();

const dj = inject(dayjsKey);
const apiUrl = inject(apiUrlKey);
const user = useUserStore();

// component properties
interface Props {
selectedEvent: Appointment & Slot,
attendeeEmail: string,
attendee: Attendee,
requested: boolean, // True if we are requesting a booking, false if already confirmed
}
defineProps<Props>();
const props = defineProps<Props>();

const heading = props.requested
? t('info.bookingSuccessfullyRequested')
: t('info.bookingSuccessfullyConfirmed');

const description = props.requested
? t('text.hostHasBeenNotified')
: t('text.timeHasBeenConfirmed', {'email': props.attendee.email});

const date = dj(props.selectedEvent.start).format('ddd') + ', '
+ dj(props.selectedEvent.start).format('MMM DD') + ' from '
+ dj(props.selectedEvent.start).format(timeFormat()) + ' – '
+ dj(props.selectedEvent.start).add(props.selectedEvent.duration, 'minutes').format(timeFormat())
+ ' (' + dj.tz.guess() + ')';

const downloadUrl = `${apiUrl}/apmt/serve/ics/${props.selectedEvent.slug}/${props.selectedEvent.id}`;

</script>

<template>
<div class="flex-center min-w-[50%] flex-col gap-12">
<div class="text-2xl font-semibold text-teal-500">
<span v-if="requested">{{ t('info.bookingSuccessfullyRequested') }}</span>
<span v-else>{{ t('info.bookingSuccessfullyConfirmed') }}</span>
<div class="booking-details">
<div class="heading">
<ph-confetti />
{{ heading }}
</div>
<div class="flex w-full max-w-sm flex-col gap-1 rounded-lg shadow-lg dark:bg-gray-800">
<div class="flex h-14 items-center justify-around rounded-t-md bg-teal-500">
<div v-for="i in 2" :key="i" class="size-4 rounded-full bg-white dark:bg-gray-600"></div>
<p>{{ description }}</p>
<div class="info">
<div class="logo">
<img src="@/assets/svg/appointment_calendar_logo.svg" alt="Appointment Calendar Logo" />
</div>
<div class="m-2 text-center text-2xl font-bold text-gray-500 dark:text-gray-300">
{{ selectedEvent.title }}
</div>
<div class="m-2 flex flex-col gap-0.5 rounded-md bg-gray-100 py-2 text-center text-gray-500 dark:bg-gray-700 dark:text-gray-300">
<div class="text-sm font-semibold text-teal-500">{{ dj(selectedEvent.start).format('dddd') }}</div>
<div class="text-lg">{{ dj(selectedEvent.start).format('LL') }}</div>
<div class="flex-center gap-2 text-sm uppercase">
<span>{{ dj(selectedEvent.start).format(timeFormat()) }}</span>
<span>{{ dj.tz.guess() }}</span>
</div>
<div>
{{ date }}
<br />
{{ t('text.virtualMeetingWith', {name: attendee.name}) }}
</div>
</div>
<primary-button
v-if="!user.authenticated"
class="btn-start mt-12 p-7"
:label="t('label.startUsingTba')"
@click="router.push({ name: 'home' })"
/>
<div class="actions">
<link-button :href="downloadUrl">
<template #iconLeft>
<ph-download-simple />
</template>
{{ t('label.downloadTheIcsFile') }}
</link-button>
</div>
</div>
<div class="appointment-call-out">
<img src="@/assets/svg/appointment_logo.svg" alt="Appointment Logo" />
<span class="tagline" v-text="t('app.tagline')"></span>
<span class="description" v-text="t('app.description')"></span>
<primary-button @click="router.push({ name: 'home' })">
{{ user.authenticated ? t('label.dashboard') : t('label.subscribe') }}
<template #iconRight>
<ph-arrow-right weight="bold" />
</template>
</primary-button>
</div>
<art-successful-booking class="m-6 h-auto w-full max-w-md sm:w-auto sm:max-w-md"/>
</template>

<style scoped>
@import '@/assets/styles/custom-media.pcss';

.booking-details {
border-radius: 1rem;
padding: 2rem 1.5rem;
max-width: 48rem;

display: flex;
flex-direction: column;
gap: 1.5rem;

background-color: var(--colour-neutral-base);
font-family: Inter, sans-serif;

.heading {
display: flex;
align-items: center;
gap: 0.5rem;

color: var(--colour-ti-highlight);
font-size: 1.5rem;
text-transform: capitalize;

svg {
fill: var(--colour-ti-highlight);
}
}

p {
color: var(--colour-ti-base);
}

.info {
display: flex;
flex-direction: column;
gap: 1.5rem;
font-size: 1.25rem;
color: var(--colour-ti-black);

.logo {
padding: 0.5rem;
border-radius: 1rem;
background-image: linear-gradient(#ffffff, #bee1fe);
flex-shrink: 0;
align-self: center;
}
}

.actions {
display: flex;

a {
padding: 0;
color: var(--colour-ti-highlight);
font-size: .75rem;
}

:deep(.base.link.filled) .icon,
:deep(.base.link.filled) .icon svg {
width: 16px !important;
height: 16px !important;
}
}
}
@media (--sm) {
.booking-details .info {
flex-direction: row;
}
}

.appointment-call-out {
display: flex;
flex-direction: column;
gap: 1.5rem;
justify-content: center;
align-items: center;

border-radius: 1rem;
padding: 1.5rem 1.5rem 3.5rem;
max-width: 23rem;

background-image: radial-gradient(circle at bottom right, #336d71, #1b222e 85%);
color: var(--colour-neutral-lower-light);
font-family: Inter, sans-serif;
text-align: center;

.tagline {
font-size: 2rem;
font-weight: 300;
font-family: Metropolis, sans-serif;
}

.description {
font-size: 0.875rem;
color: var(--colour-neutral-lower-light);
}

:deep(.base.primary.filled) {
position: relative;
z-index: 1;
background-image: linear-gradient(161deg, #a0e1ff -26%, #2b8cdc 45%);
color: var(--colour-ti-base-light);
text-transform: uppercase;
font-weight: 600;
font-size: 0.8125rem;

&::before {
content: '';
position: absolute;
z-index: -1;
width: calc(100% - 2px);
height: calc(100% - 2px);
background-image: linear-gradient(353deg, #1373d9 -36%, #58c9ff);
border-radius: .5rem;
}
}
}
</style>
Loading