@mundogamernetwork/shared-ui 1.1.96 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,135 @@
1
+ <script setup lang="ts">
2
+ // Join-call panel for a scheduled/completed moderated session — surfaces
3
+ // Meeting::meeting_link (eager-loaded server-side via
4
+ // PlaytestModeratedSessionRepository::findWithRelations(), confirmed this
5
+ // session), plus the confirm/no-show/cancel actions. NoShow is deliberately
6
+ // NOT exposed here as a tester self-action — markNoShow() is a
7
+ // moderator-facing action in the backend's own doc comments; kept in the
8
+ // service for completeness (per the task list) but this panel only wires
9
+ // confirm/cancel for the tester's own use.
10
+ import { computed } from "vue";
11
+ import type { PlaytestModeratedSession } from "../../../services/playtestTesterService";
12
+
13
+ const props = defineProps<{
14
+ session: PlaytestModeratedSession | null;
15
+ acting?: boolean;
16
+ }>();
17
+
18
+ const emit = defineEmits<{
19
+ (e: "confirm"): void
20
+ (e: "cancel"): void
21
+ }>();
22
+
23
+ const canJoin = computed(() => props.session?.status === "scheduled" && !!props.session?.meeting?.meeting_link);
24
+ const canConfirm = computed(() => props.session?.status === "scheduled" && !props.session?.tester_confirmed_at);
25
+ const canCancel = computed(() => props.session?.status === "scheduled");
26
+
27
+ const scheduledAt = computed(() => (props.session?.scheduled_at ? new Date(props.session.scheduled_at) : null));
28
+ </script>
29
+
30
+ <template>
31
+ <div class="join-call-panel">
32
+ <template v-if="session">
33
+ <h3 class="join-call-panel__title">{{ session.campaign?.title ?? $t("playtest.moderated.session_title", "Moderated session") }}</h3>
34
+
35
+ <p class="join-call-panel__time">{{ scheduledAt?.toLocaleString() }}</p>
36
+
37
+ <div v-if="session.tester_confirmed_at" class="join-call-panel__confirmed-badge">
38
+ <i class="fa-solid fa-circle-check" />
39
+ {{ $t("playtest.moderated.you_confirmed", "You've confirmed attendance") }}
40
+ </div>
41
+
42
+ <a
43
+ v-if="canJoin"
44
+ :href="session.meeting!.meeting_link!"
45
+ target="_blank"
46
+ rel="noopener"
47
+ class="btn primary join-call-panel__join"
48
+ >
49
+ {{ $t("playtest.moderated.join_call", "Join call") }}
50
+ </a>
51
+ <p v-else-if="session.status === 'scheduled'" class="join-call-panel__no-link">
52
+ {{ $t("playtest.moderated.link_pending", "The call link will appear here closer to the session.") }}
53
+ </p>
54
+
55
+ <div class="join-call-panel__actions">
56
+ <button
57
+ v-if="canConfirm"
58
+ type="button"
59
+ class="btn secondary"
60
+ :disabled="acting"
61
+ @click="emit('confirm')"
62
+ >
63
+ {{ $t("playtest.moderated.confirm_attendance", "Confirm attendance") }}
64
+ </button>
65
+ <button
66
+ v-if="canCancel"
67
+ type="button"
68
+ class="btn secondary join-call-panel__cancel"
69
+ :disabled="acting"
70
+ @click="emit('cancel')"
71
+ >
72
+ {{ $t("playtest.moderated.cancel_session", "Cancel") }}
73
+ </button>
74
+ </div>
75
+ </template>
76
+
77
+ <p v-else class="join-call-panel__empty">
78
+ {{ $t("playtest.moderated.no_session", "No session selected.") }}
79
+ </p>
80
+ </div>
81
+ </template>
82
+
83
+ <style scoped lang="scss">
84
+ .join-call-panel {
85
+ padding: 20px;
86
+ border: 1px solid var(--button-secondary-default-bg, #2a2a2a);
87
+ display: flex;
88
+ flex-direction: column;
89
+ gap: 12px;
90
+
91
+ &__title {
92
+ font-size: 16px;
93
+ font-weight: 700;
94
+ color: var(--card-article-title, #fff);
95
+ margin: 0;
96
+ }
97
+
98
+ &__time {
99
+ font-size: 13px;
100
+ color: var(--secondary-info-fg, #aaa);
101
+ margin: 0;
102
+ }
103
+
104
+ &__confirmed-badge {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 6px;
108
+ font-size: 12px;
109
+ color: #8ac53d;
110
+ }
111
+
112
+ &__join {
113
+ width: fit-content;
114
+ }
115
+
116
+ &__no-link {
117
+ font-size: 12px;
118
+ color: var(--secondary-info-fg, #888);
119
+ }
120
+
121
+ &__actions {
122
+ display: flex;
123
+ gap: 8px;
124
+ }
125
+
126
+ &__cancel {
127
+ color: var(--danger, #e53935);
128
+ }
129
+
130
+ &__empty {
131
+ font-size: 13px;
132
+ color: var(--secondary-info-fg, #888);
133
+ }
134
+ }
135
+ </style>
@@ -0,0 +1,146 @@
1
+ <script setup lang="ts">
2
+ // Card for a single moderated slot (claimable) or an already-booked session
3
+ // (status-aware actions: confirm/cancel/no-show). Dual-purpose by design —
4
+ // consuming pages (slot browse vs. my-sessions list) pass whichever shape
5
+ // they have; `variant` picks the right action set.
6
+ import { computed } from "vue";
7
+ import type { PlaytestModeratedSlot, PlaytestModeratedSession } from "../../../services/playtestTesterService";
8
+
9
+ const props = defineProps<{
10
+ slot?: PlaytestModeratedSlot;
11
+ session?: PlaytestModeratedSession;
12
+ variant: "slot" | "session";
13
+ claiming?: boolean;
14
+ acting?: boolean;
15
+ }>();
16
+
17
+ const emit = defineEmits<{
18
+ (e: "claim", slotId: number): void
19
+ (e: "confirm", sessionId: number): void
20
+ (e: "cancel", sessionId: number): void
21
+ }>();
22
+
23
+ const startsAt = computed(() => {
24
+ const raw = props.variant === "slot" ? props.slot?.starts_at : props.session?.scheduled_at;
25
+ return raw ? new Date(raw) : null;
26
+ });
27
+
28
+ const formattedDate = computed(() => startsAt.value?.toLocaleString() ?? "");
29
+
30
+ const durationMinutes = computed(() =>
31
+ props.variant === "slot" ? props.slot?.duration_minutes : props.session?.duration_minutes
32
+ );
33
+
34
+ const statusLabel = computed(() => props.session?.status ?? null);
35
+
36
+ const canConfirm = computed(() =>
37
+ props.variant === "session" &&
38
+ props.session?.status === "scheduled" &&
39
+ !props.session?.tester_confirmed_at
40
+ );
41
+
42
+ const canCancel = computed(() => props.variant === "session" && props.session?.status === "scheduled");
43
+ </script>
44
+
45
+ <template>
46
+ <div class="session-card">
47
+ <div class="session-card__header">
48
+ <span class="session-card__date">{{ formattedDate }}</span>
49
+ <span v-if="durationMinutes" class="session-card__duration">{{ durationMinutes }} min</span>
50
+ </div>
51
+
52
+ <div v-if="statusLabel" class="session-card__status" :class="`session-card__status--${statusLabel}`">
53
+ {{ statusLabel.replace('_', ' ') }}
54
+ <span v-if="session?.tester_confirmed_at" class="session-card__confirmed">
55
+ · {{ $t("playtest.moderated.confirmed", "confirmed") }}
56
+ </span>
57
+ </div>
58
+
59
+ <div class="session-card__actions">
60
+ <button
61
+ v-if="variant === 'slot'"
62
+ type="button"
63
+ class="btn primary"
64
+ :disabled="claiming"
65
+ @click="emit('claim', slot!.id)"
66
+ >
67
+ {{ $t("playtest.moderated.claim_slot", "Claim this slot") }}
68
+ </button>
69
+
70
+ <template v-else>
71
+ <button
72
+ v-if="canConfirm"
73
+ type="button"
74
+ class="btn primary"
75
+ :disabled="acting"
76
+ @click="emit('confirm', session!.id)"
77
+ >
78
+ {{ $t("playtest.moderated.confirm_attendance", "Confirm attendance") }}
79
+ </button>
80
+ <button
81
+ v-if="canCancel"
82
+ type="button"
83
+ class="btn secondary"
84
+ :disabled="acting"
85
+ @click="emit('cancel', session!.id)"
86
+ >
87
+ {{ $t("playtest.moderated.cancel_session", "Cancel") }}
88
+ </button>
89
+ </template>
90
+ </div>
91
+ </div>
92
+ </template>
93
+
94
+ <style scoped lang="scss">
95
+ .session-card {
96
+ padding: 14px 16px;
97
+ background: var(--bg-app-badge, #1a1a1a);
98
+ border: 1px solid var(--button-secondary-default-bg, #2a2a2a);
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: 10px;
102
+
103
+ &__header {
104
+ display: flex;
105
+ align-items: baseline;
106
+ justify-content: space-between;
107
+ }
108
+
109
+ &__date {
110
+ font-size: 14px;
111
+ font-weight: 600;
112
+ color: var(--text-primary, #fff);
113
+ }
114
+
115
+ &__duration {
116
+ font-size: 12px;
117
+ color: var(--secondary-info-fg, #aaa);
118
+ }
119
+
120
+ &__status {
121
+ display: inline-flex;
122
+ width: fit-content;
123
+ font-size: 11px;
124
+ font-weight: 700;
125
+ text-transform: uppercase;
126
+ padding: 2px 8px;
127
+ color: var(--secondary-info-fg, #aaa);
128
+ border: 1px solid var(--border-color, rgba(255, 255, 255, 0.2));
129
+
130
+ &--scheduled { color: var(--primary, #D297FF); border-color: var(--primary, #D297FF); }
131
+ &--completed { color: #8ac53d; border-color: #8ac53d; }
132
+ &--no_show,
133
+ &--cancelled { color: var(--danger, #e53935); border-color: var(--danger, #e53935); }
134
+ }
135
+
136
+ &__confirmed {
137
+ text-transform: none;
138
+ font-weight: 400;
139
+ }
140
+
141
+ &__actions {
142
+ display: flex;
143
+ gap: 8px;
144
+ }
145
+ }
146
+ </style>