Skip to content

Commit a78e56d

Browse files
committed
Clean up control of clearing data #869
1 parent 33ec325 commit a78e56d

File tree

9 files changed

+830
-74
lines changed

9 files changed

+830
-74
lines changed

web-client/src/App.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import { useTourStore } from '@/stores/tourStore.js'
3030
import { useViewportHeight } from '@/composables/useViewportHeight'
3131
import { useSlideruleDefaults } from '@/stores/defaultsStore'
3232
import { createLogger } from '@/utils/logger'
33+
import { usePrivacyConsentStore } from '@/stores/privacyConsentStore'
34+
import SrConsentBanner from '@/components/SrConsentBanner.vue'
3335
3436
const logger = createLogger('App')
3537
@@ -42,6 +44,7 @@ const sysConfigStore = useSysConfigStore()
4244
const legacyJwtStore = useLegacyJwtStore()
4345
const authDialogStore = useAuthDialogStore()
4446
const googleApiKeyStore = useGoogleApiKeyStore()
47+
const privacyConsentStore = usePrivacyConsentStore()
4548
const route = useRoute()
4649
4750
// Global login dialog state
@@ -348,6 +351,7 @@ onMounted(async () => {
348351
checkUnsupported()
349352
checkPrivateClusterAuth()
350353
tourStore.checkSeen()
354+
privacyConsentStore.initializeOnStartup()
351355
await nextTick()
352356
353357
if (!tourStore.hasSeenIntro) {
@@ -694,6 +698,7 @@ async function handleLongTourButtonClick() {
694698
<div class="sliderule-content">
695699
<RouterView />
696700
</div>
701+
<SrConsentBanner />
697702

698703
<!-- Dialog for displaying version information -->
699704
<Dialog

web-client/src/components/SrClearCache.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ const clearCacheAndReload = async () => {
3939

4040
<template>
4141
<div class="cache-reload">
42-
<button :disabled="isClearing" @click="clearCacheAndReload">
42+
<button
43+
:disabled="isClearing"
44+
title="Reloads the application code only (does not delete your data)"
45+
@click="clearCacheAndReload"
46+
>
4347
{{ isClearing ? 'Clearing...' : 'Empty Cache & Hard Reload' }}
4448
</button>
4549
<p v-if="message">{{ message }}</p>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
import { useRouter } from 'vue-router'
4+
import Button from 'primevue/button'
5+
import { usePrivacyConsentStore } from '@/stores/privacyConsentStore'
6+
7+
const router = useRouter()
8+
const privacyConsentStore = usePrivacyConsentStore()
9+
10+
const showBanner = computed(() => privacyConsentStore.shouldShowBanner)
11+
12+
function acknowledge() {
13+
privacyConsentStore.acknowledge()
14+
}
15+
16+
function goToPrivacyPolicy() {
17+
void router.push('/privacy')
18+
}
19+
</script>
20+
21+
<template>
22+
<Transition name="slide-up">
23+
<div v-if="showBanner" class="sr-consent-banner">
24+
<div class="sr-consent-content">
25+
<div class="sr-consent-text">
26+
<p>
27+
We use browser storage to save your preferences and authenticate your session.
28+
<a href="#" class="sr-privacy-link" @click.prevent="goToPrivacyPolicy">Learn more</a>
29+
</p>
30+
</div>
31+
<div class="sr-consent-buttons">
32+
<Button label="I Understand" class="sr-consent-btn" @click="acknowledge" />
33+
</div>
34+
</div>
35+
</div>
36+
</Transition>
37+
</template>
38+
39+
<style scoped>
40+
.sr-consent-banner {
41+
position: fixed;
42+
bottom: 0;
43+
left: 0;
44+
right: 0;
45+
background: var(--p-surface-800, #1e1e1e);
46+
border-top: 1px solid var(--p-surface-600, #3e3e3e);
47+
padding: 1rem 1.5rem;
48+
z-index: 1001;
49+
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.3);
50+
}
51+
52+
.sr-consent-content {
53+
max-width: 1200px;
54+
margin: 0 auto;
55+
display: flex;
56+
flex-wrap: wrap;
57+
align-items: center;
58+
justify-content: space-between;
59+
gap: 1rem;
60+
}
61+
62+
.sr-consent-text {
63+
flex: 1;
64+
min-width: 200px;
65+
}
66+
67+
.sr-consent-text p {
68+
margin: 0;
69+
color: var(--p-text-color, #e0e0e0);
70+
font-size: 0.9rem;
71+
line-height: 1.5;
72+
}
73+
74+
.sr-gpc-notice {
75+
color: var(--p-primary-color, #60a5fa);
76+
font-weight: 500;
77+
}
78+
79+
.sr-privacy-link {
80+
color: var(--p-primary-color, #60a5fa);
81+
text-decoration: underline;
82+
cursor: pointer;
83+
}
84+
85+
.sr-privacy-link:hover {
86+
color: var(--p-primary-400, #93c5fd);
87+
}
88+
89+
.sr-consent-buttons {
90+
display: flex;
91+
gap: 0.75rem;
92+
flex-shrink: 0;
93+
}
94+
95+
/* Ensure buttons have equal visual weight (GDPR compliance) */
96+
.sr-consent-btn {
97+
min-width: 130px;
98+
font-weight: 500;
99+
}
100+
101+
/* Slide-up transition */
102+
.slide-up-enter-active,
103+
.slide-up-leave-active {
104+
transition:
105+
transform 0.3s ease,
106+
opacity 0.3s ease;
107+
}
108+
109+
.slide-up-enter-from,
110+
.slide-up-leave-to {
111+
transform: translateY(100%);
112+
opacity: 0;
113+
}
114+
115+
/* Responsive: stack on mobile */
116+
@media (max-width: 600px) {
117+
.sr-consent-content {
118+
flex-direction: column;
119+
text-align: center;
120+
}
121+
122+
.sr-consent-buttons {
123+
width: 100%;
124+
justify-content: center;
125+
}
126+
127+
.sr-consent-btn {
128+
flex: 1;
129+
min-width: 0;
130+
}
131+
}
132+
</style>
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
import { useRouter } from 'vue-router'
4+
import { useConfirm } from 'primevue/useconfirm'
5+
import { useToast } from 'primevue/usetoast'
6+
import Button from 'primevue/button'
7+
import ConfirmDialog from 'primevue/confirmdialog'
8+
import { usePrivacyConsentStore } from '@/stores/privacyConsentStore'
9+
import { useSrToastStore } from '@/stores/srToastStore'
10+
import { cleanupDelAllRequests } from '@/utils/storageUtils'
11+
12+
const router = useRouter()
13+
const confirm = useConfirm()
14+
const toast = useToast()
15+
const privacyConsentStore = usePrivacyConsentStore()
16+
const srToastStore = useSrToastStore()
17+
18+
const consentStatus = computed(() => privacyConsentStore.consentStatus)
19+
const consentDate = computed(() => {
20+
if (!privacyConsentStore.consentTimestamp) return null
21+
return new Date(privacyConsentStore.consentTimestamp).toLocaleDateString()
22+
})
23+
24+
function goToPrivacyPolicy() {
25+
void router.push('/privacy')
26+
}
27+
28+
function confirmClearData() {
29+
confirm.require({
30+
message:
31+
'This will clear your authentication, preferences, and consent settings. You will need to log in again. Your science data (records and parquet files) will be preserved. Are you sure?',
32+
header: 'Clear All Data',
33+
icon: 'pi pi-exclamation-triangle',
34+
rejectLabel: 'Cancel',
35+
acceptLabel: 'Clear All Data',
36+
rejectClass: 'p-button-secondary',
37+
acceptClass: 'p-button-danger',
38+
accept: async () => {
39+
await privacyConsentStore.clearAllUserData()
40+
toast.add({
41+
severity: 'success',
42+
summary: 'Data Cleared',
43+
detail: 'Authentication and preferences cleared. Science data preserved.',
44+
life: srToastStore.getLife()
45+
})
46+
// Redirect to home page after clearing
47+
void router.push('/')
48+
}
49+
})
50+
}
51+
52+
function confirmClearScienceData() {
53+
confirm.require({
54+
message:
55+
'This will delete all your request records and cached parquet data files. Your authentication and preferences will be preserved. Are you sure?',
56+
header: 'Clear Science Data',
57+
icon: 'pi pi-exclamation-triangle',
58+
rejectLabel: 'Cancel',
59+
acceptLabel: 'Clear Science Data',
60+
rejectClass: 'p-button-secondary',
61+
acceptClass: 'p-button-warning',
62+
accept: async () => {
63+
await cleanupDelAllRequests()
64+
toast.add({
65+
severity: 'success',
66+
summary: 'Science Data Cleared',
67+
detail: 'All request records and parquet files have been removed.',
68+
life: srToastStore.getLife()
69+
})
70+
}
71+
})
72+
}
73+
</script>
74+
75+
<template>
76+
<div class="sr-privacy-settings">
77+
<ConfirmDialog />
78+
79+
<!-- Current Status -->
80+
<div class="sr-privacy-status">
81+
<div class="sr-privacy-status-row">
82+
<span class="sr-privacy-label">Privacy notice:</span>
83+
<span class="sr-privacy-value">{{ consentStatus }}</span>
84+
</div>
85+
<div v-if="consentDate" class="sr-privacy-status-row">
86+
<span class="sr-privacy-label">Acknowledged:</span>
87+
<span class="sr-privacy-value">{{ consentDate }}</span>
88+
</div>
89+
</div>
90+
91+
<!-- Actions -->
92+
<div class="sr-privacy-actions">
93+
<Button
94+
label="Clear All My Data"
95+
icon="pi pi-trash"
96+
severity="danger"
97+
outlined
98+
title="Clears authentication, preferences, and consent settings. Preserves your science data (records and parquet files)."
99+
@click="confirmClearData"
100+
/>
101+
<Button
102+
label="Clear Science Data"
103+
icon="pi pi-database"
104+
severity="warning"
105+
outlined
106+
title="Clears all request records and cached parquet files. Preserves your authentication and preferences."
107+
@click="confirmClearScienceData"
108+
/>
109+
</div>
110+
111+
<!-- Privacy Policy Link -->
112+
<div class="sr-privacy-link-section">
113+
<Button
114+
label="View Privacy Policy"
115+
icon="pi pi-external-link"
116+
link
117+
@click="goToPrivacyPolicy"
118+
/>
119+
</div>
120+
</div>
121+
</template>
122+
123+
<style scoped>
124+
.sr-privacy-settings {
125+
display: flex;
126+
flex-direction: column;
127+
gap: 1rem;
128+
}
129+
130+
.sr-privacy-status {
131+
display: flex;
132+
flex-direction: column;
133+
gap: 0.5rem;
134+
padding: 1rem;
135+
background: var(--p-surface-800, #1e1e1e);
136+
border: 1px solid var(--p-surface-600, #3e3e3e);
137+
border-radius: 8px;
138+
}
139+
140+
.sr-privacy-status-row {
141+
display: flex;
142+
justify-content: space-between;
143+
align-items: center;
144+
}
145+
146+
.sr-privacy-label {
147+
color: var(--p-text-muted-color, #888);
148+
}
149+
150+
.sr-privacy-value {
151+
font-weight: 500;
152+
}
153+
154+
.sr-privacy-actions {
155+
display: flex;
156+
gap: 0.75rem;
157+
flex-wrap: wrap;
158+
justify-content: center;
159+
}
160+
161+
.sr-privacy-link-section {
162+
display: flex;
163+
justify-content: center;
164+
border-top: 1px solid var(--p-surface-600, #3e3e3e);
165+
padding-top: 1rem;
166+
}
167+
</style>

0 commit comments

Comments
 (0)