alpha
Login
or
Join now
danielroe.dev
/
dwaring87-webdev-wedding
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
[READ-ONLY] Mirror of https://github.com/danielroe/dwaring87-webdev-wedding. Marissa & David's Wedding Website!
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
RSVP: Refactor Edit / Update logic
author
David Waring
date
3 years ago
(Mar 12, 2023, 6:37 PM -0400)
commit
44230c35
44230c3513afdd419b70e7bd018fdbca620237ed
parent
f06bb35b
f06bb35b72ad338b947c5c883ef886578f225918
+73
-63
4 changed files
Expand all
Collapse all
Unified
Split
components
Form
Check.vue
Toggle.vue
RSVP
Edit.vue
Loading.vue
+23
-17
components/Form/Check.vue
View file
Reviewed
···
1
1
-
<script setup>
2
2
-
import RiCheck from '~icons/ri/checkbox-circle-line';
3
3
-
import RiCheckBlank from '~icons/ri/checkbox-blank-circle-line';
4
4
-
5
5
-
defineProps({
6
6
-
code: String,
7
7
-
label: String,
8
8
-
enabled: Boolean
9
9
-
});
10
10
-
</script>
11
11
-
12
12
-
<template>
13
13
-
<button :data-code="code" :data-label="label" :data-enabled="enabled" @click="enabled = !enabled" :class="{'border-cyan-700': enabled, 'border-gray-100': !enabled}" class="flex flex-row items-center gap-2 p-4 rounded-md shadow-md bg-white cursor-pointer border-2 w-full sm:w-fit hover:bg-gray-50">
14
14
-
<RiCheck class="text-cyan-800" v-if="enabled" />
15
15
-
<RiCheckBlank class="text-cyan-800" v-else />
16
16
-
<p :class="{'text-cyan-800 font-bold': enabled}">{{ label }}</p>
17
17
-
</button>
1
1
+
<script setup>
2
2
+
import RiCheck from '~icons/ri/checkbox-circle-line';
3
3
+
import RiCheckBlank from '~icons/ri/checkbox-blank-circle-line';
4
4
+
5
5
+
const props = defineProps({
6
6
+
code: String,
7
7
+
label: String,
8
8
+
enabled: Boolean
9
9
+
});
10
10
+
const emit = defineEmits(['check']);
11
11
+
12
12
+
const state = ref(props.enabled);
13
13
+
watch(state, (newState) => {
14
14
+
emit('check', newState);
15
15
+
});
16
16
+
</script>
17
17
+
18
18
+
<template>
19
19
+
<button :data-code="code" :data-label="label" :data-enabled="state" @click="state = !state" :class="{'border-cyan-700': state, 'border-gray-100': !state}" class="flex flex-row items-center gap-2 p-4 rounded-md shadow-md bg-white cursor-pointer border-2 w-full sm:w-fit hover:bg-gray-50">
20
20
+
<RiCheck class="text-cyan-800" v-if="state" />
21
21
+
<RiCheckBlank class="text-cyan-800" v-else />
22
22
+
<p :class="{'text-cyan-800 font-bold': state}">{{ label }}</p>
23
23
+
</button>
18
24
</template>
+13
-7
components/Form/Toggle.vue
View file
Reviewed
···
13
13
default: "YES"
14
14
}
15
15
});
16
16
+
const emit = defineEmits(['toggle']);
17
17
+
18
18
+
const state = ref(props.enabled);
19
19
+
watch(state, (newState) => {
20
20
+
emit('toggle', newState);
21
21
+
});
16
22
</script>
17
23
18
24
<template>
19
19
-
<div class="flex gap-2" :data-enabled="enabled">
20
20
-
<p :class="{'text-black font-bold': !enabled, 'text-gray-400 italic': enabled}" class="cursor-pointer" @click="enabled = false">{{ label_no }}</p>
21
21
-
<button type="button" @click="enabled = !enabled" :class="{'bg-gradient-to-r from-cyan-700 to-cyan-800': enabled, 'bg-gray-200': !enabled}" class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-cyan-700 focus:ring-offset-2" role="switch" aria-checked="false">
22
22
-
<span :class="{'translate-x-5': enabled, 'translate-x-0': !enabled}" class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out">
23
23
-
<span :class="{'opacity-0 ease-out duration-100': enabled, 'opacity-100 ease-in duration-200': !enabled}" class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" aria-hidden="true">
25
25
+
<div class="flex gap-2" :data-enabled="state">
26
26
+
<p :class="{'text-black font-bold': !state, 'text-gray-400 italic': state}" class="cursor-pointer" @click="state = false">{{ label_no }}</p>
27
27
+
<button type="button" @click="state = !state" :class="{'bg-gradient-to-r from-cyan-700 to-cyan-800': state, 'bg-gray-200': !state}" class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-cyan-700 focus:ring-offset-2" role="switch" aria-checked="false">
28
28
+
<span :class="{'translate-x-5': state, 'translate-x-0': !state}" class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out">
29
29
+
<span :class="{'opacity-0 ease-out duration-100': state, 'opacity-100 ease-in duration-200': !state}" class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" aria-hidden="true">
24
30
<svg class="h-3 w-3 text-gray-400" fill="none" viewBox="0 0 12 12">
25
31
<path d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
26
32
</svg>
27
33
</span>
28
28
-
<span :class="{'opacity-100 ease-in duration-200': enabled, 'opacity-0 ease-out duration-100': !enabled}" class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" aria-hidden="true">
34
34
+
<span :class="{'opacity-100 ease-in duration-200': state, 'opacity-0 ease-out duration-100': !state}" class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" aria-hidden="true">
29
35
<svg class="h-3 w-3 text-cyan-800" fill="currentColor" viewBox="0 0 12 12">
30
36
<path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
31
37
</svg>
32
38
</span>
33
39
</span>
34
40
</button>
35
35
-
<p :class="{'text-cyan-800 font-bold': enabled, 'text-gray-400 italic': !enabled}" class="cursor-pointer" @click="enabled = true">{{ label_yes }}</p>
41
41
+
<p :class="{'text-cyan-800 font-bold': state, 'text-gray-400 italic': !state}" class="cursor-pointer" @click="state = true">{{ label_yes }}</p>
36
42
</div>
37
43
</template>
+36
-38
components/RSVP/Edit.vue
View file
Reviewed
···
6
6
invitation: Object
7
7
});
8
8
const emit = defineEmits(['cancel']);
9
9
+
const guests = ref(JSON.parse(JSON.stringify(props.invitation.guests)));
9
10
10
11
const email = await getDetails("contact_email");
11
12
const DIET = {
···
17
18
dairy: "Dairy Allergy",
18
19
other: "Other"
19
20
}
21
21
+
const updateDietaryRestriction = (guest, key, enabled) => {
22
22
+
if ( enabled ) {
23
23
+
if ( guest.dietary_restrictions ) {
24
24
+
guest.dietary_restrictions.push(key);
25
25
+
}
26
26
+
else {
27
27
+
guest.dietary_restrictions = [key];
28
28
+
}
29
29
+
}
30
30
+
else {
31
31
+
guest.dietary_restrictions = guest.dietary_restrictions.filter((item) => item !== key);
32
32
+
}
33
33
+
}
20
34
21
35
const cancel = () => {
22
36
emit('cancel');
···
39
53
40
54
window.scrollTo(0, 0);
41
55
42
42
-
for ( const guest of props.invitation.guests ) {
43
43
-
let id = guest.id;
44
44
-
let container = document.getElementById(`guest-container-${id}`);
45
45
-
46
46
-
let name = container.getElementsByClassName('guest-name')[0].value;
47
47
-
let email = container.getElementsByClassName('guest-email')[0].value;
48
48
-
let rsvp_welcome = container.getElementsByClassName('guest-rsvp-welcome')[0].dataset.enabled === 'true';
49
49
-
let rsvp = container.getElementsByClassName('guest-rsvp')[0].dataset.enabled === 'true';
50
50
-
let dietary_restrictions = [];
51
51
-
let diet_els = container.getElementsByClassName('guest-diet');
52
52
-
for ( let i = 0; i < diet_els.length; i++ ) {
53
53
-
if ( diet_els[i].dataset.enabled && diet_els[i].dataset.enabled === 'true' ) dietary_restrictions.push(diet_els[i].dataset.code);
56
56
+
guests.value.forEach(async (guest, index) => {
57
57
+
const original = props.invitation.guests[index];
58
58
+
const updated_guest_props = {
59
59
+
name: guest.name !== original.name ? guest.name : undefined,
60
60
+
email: guest.email !== original.email ? guest.email : undefined,
61
61
+
rsvp_welcome: guest.rsvp_welcome !== original.rsvp_welcome ? guest.rsvp_welcome : undefined,
62
62
+
rsvp: guest.rsvp !== original.rsvp ? guest.rsvp : undefined,
63
63
+
dietary_restrictions: JSON.stringify(guest.dietary_restrictions) !== JSON.stringify(original.dietary_restrictions) ? guest.dietary_restrictions : undefined,
64
64
+
notes: guest.notes !== original.notes ? guest.notes : undefined
54
65
}
55
55
-
let notes = container.getElementsByClassName('guest-notes')[0].value;
56
56
-
57
57
-
if ( rsvp || rsvp_welcome ) {
66
66
+
if ( guest.rsvp || guest.rsvp_welcome ) {
58
67
attending.value = true;
59
68
}
60
69
61
61
-
let success = await updateGuest(id, {
62
62
-
name: name !== guest.name ? name : undefined,
63
63
-
email: email !== guest.email ? email : undefined,
64
64
-
rsvp_welcome: rsvp_welcome !== guest.rsvp_welcome ? rsvp_welcome : undefined,
65
65
-
rsvp: rsvp !== guest.rsvp ? rsvp : undefined,
66
66
-
dietary_restrictions:
67
67
-
!guest.dietary_restrictions ||
68
68
-
dietary_restrictions.length !== guest.dietary_restrictions.length ||
69
69
-
!dietary_restrictions.every((v, i) => v === guest.dietary_restrictions[i])
70
70
-
? dietary_restrictions
71
71
-
: undefined,
72
72
-
notes: notes !== guest.notes ? notes : undefined
73
73
-
});
70
70
+
let success = await updateGuest(guest.id, updated_guest_props);
74
71
await sleep(500);
75
75
-
76
72
if ( !success ) {
77
73
errors.push(`Could not update Guest <strong><em>${name}</em></strong>.`);
78
78
-
useTrackEvent('RSVP Error', { props: { code: props.invitation.invite_code, guest: name } });
74
74
+
useTrackEvent('RSVP Error', { props: { code: props.invitation.invite_code, guest: guest.name } });
79
75
}
80
80
-
}
76
76
+
});
81
77
82
78
if ( errors.length > 0 ) {
83
79
error.value = errors.join(`<br />`);
···
104
100
/>
105
101
106
102
<div v-show="!saving">
107
107
-
<div class="mx-1 sm:mx-2 md:mx-4 mt-4 mb-8 px-4 bg-gray-100 border border-gray-400 rounded-md shadow" :id="`guest-container-${guest.id}`" v-for="(guest) in invitation.guests" :key="guest.id">
103
103
+
<div class="mx-1 sm:mx-2 md:mx-4 mt-4 mb-8 px-4 bg-gray-100 border border-gray-400 rounded-md shadow" :id="`guest-container-${guest.id}`" v-for="(guest) in guests" :key="guest.id">
108
104
<div class="group">
109
105
<p>Name:</p>
110
110
-
<input class="guest-name" :value="guest.name" />
106
106
+
<input class="guest-name" v-model="guest.name" @input="onInput" />
111
107
</div>
112
108
<div class="group">
113
109
<p>Email:</p>
114
114
-
<input class="guest-email" :value="guest.email" />
110
110
+
<input class="guest-email" v-model="guest.email" />
115
111
<p class="info">Enter your email to receive updates from us about the wedding</p>
116
112
</div>
117
113
<div class="group">
118
114
<p>RSVP (Friday Welcome Dinner):</p>
119
119
-
<FormToggle class="guest-rsvp-welcome" :enabled="guest.rsvp_welcome" />
115
115
+
<FormToggle class="guest-rsvp-welcome" :enabled="guest.rsvp_welcome" @toggle="(s) => guest.rsvp_welcome = s" />
120
116
<p class="info">Will you be attending the welcome dinner (potluck) the Friday evening before the wedding?</p>
121
117
</div>
122
118
<div class="group">
123
119
<p>RSVP (Saturday Ceremony & Reception):</p>
124
124
-
<FormToggle class="guest-rsvp" :enabled="guest.rsvp" />
120
120
+
<FormToggle class="guest-rsvp" :enabled="guest.rsvp" @toggle="(s) => guest.rsvp = s" />
125
121
<p class="info">Will you be attending the wedding ceremony and reception on Saturday?</p>
126
122
</div>
127
123
<div class="group">
128
124
<p>Dietary Restrictions:</p>
129
125
<div class="flex flex-wrap gap-2">
130
130
-
<FormCheck class="guest-diet" v-for="(value, key) in DIET" :code="key" :label="value" :enabled="guest.dietary_restrictions && guest.dietary_restrictions.includes(key)" />
126
126
+
<FormCheck class="guest-diet" v-for="(value, key) in DIET" :code="key" :label="value"
127
127
+
:enabled="guest.dietary_restrictions && guest.dietary_restrictions.includes(key)"
128
128
+
@check="(s) => updateDietaryRestriction(guest, key, s)" />
131
129
</div>
132
130
</div>
133
131
<div class="group">
134
132
<p>Comments:</p>
135
135
-
<textarea class="guest-notes" rows="5">{{ guest.notes }}</textarea>
133
133
+
<textarea class="guest-notes" v-model="guest.notes" rows="5"></textarea>
136
134
<p class="info">Any other information we should know?</p>
137
135
</div>
138
136
</div>
+1
-1
components/RSVP/Loading.vue
View file
Reviewed
···
70
70
</ul>
71
71
</div>
72
72
</div>
73
73
-
<Image class="mx-auto mt-8 max-w-sm max-h-sm rounded-md shadow-md" d-key="rsvp-success" />
73
73
+
<Image d-key="rsvp-success" class="mx-auto mt-8 max-w-sm max-h-sm rounded-md shadow-md" />
74
74
<br />
75
75
<button class="btn-dark mx-auto" @click="emit('continue')" v-html="continueLabel"></button>
76
76
</div>