Recent sheets per user

This commit is contained in:
Roman Krček
2025-09-08 15:45:36 +02:00
parent c6c3bbc024
commit e43101648b
4 changed files with 77 additions and 19 deletions

View File

@@ -1,5 +1,11 @@
<script lang="ts"> <script lang="ts">
import { selectedSheet, currentStep, columnMapping } from '$lib/stores'; import {
selectedSheet,
currentStep,
columnMapping,
} from '$lib/stores';
import { userEmail } from '$lib/google';
import { hashString } from '$lib/utils';
import type { ColumnMappingType, SheetInfoType } from '$lib/stores'; import type { ColumnMappingType, SheetInfoType } from '$lib/stores';
import { getSheetNames, getSheetData, ensureToken } from '$lib/google'; import { getSheetNames, getSheetData, ensureToken } from '$lib/google';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@@ -33,7 +39,13 @@
{ key: 'alreadyPrinted', label: 'Already Printed', required: false } { key: 'alreadyPrinted', label: 'Already Printed', required: false }
]; ];
const RECENT_SHEETS_KEY = 'recentSheets'; async function getRecentSheetsKey() {
const email = $userEmail;
if (email) {
return `recentSheets_${await hashString(email)}`;
}
return 'recentSheets_anonymous';
}
// Load available sheets when component mounts // Load available sheets when component mounts
onMount(async () => { onMount(async () => {
@@ -42,7 +54,8 @@
console.log('Selected sheet on mount:', $selectedSheet); console.log('Selected sheet on mount:', $selectedSheet);
// Check if we already have saved mapping data // Check if we already have saved mapping data
const recentSheetsData = localStorage.getItem(RECENT_SHEETS_KEY); const key = await getRecentSheetsKey();
const recentSheetsData = localStorage.getItem(key);
if (recentSheetsData) { if (recentSheetsData) {
try { try {
@@ -161,7 +174,7 @@
autoMapColumns(); autoMapColumns();
// Check if we have saved column mapping for this sheet // Check if we have saved column mapping for this sheet
loadSavedColumnMapping(); await loadSavedColumnMapping();
} else { } else {
error = 'The selected sheet appears to be empty.'; error = 'The selected sheet appears to be empty.';
console.warn('Sheet is empty'); console.warn('Sheet is empty');
@@ -235,14 +248,15 @@
updateMappingStatus(); updateMappingStatus();
} }
function loadSavedColumnMapping() { async function loadSavedColumnMapping() {
if (!$selectedSheet || !selectedSheetName) { if (!$selectedSheet || !selectedSheetName) {
console.log('Cannot load saved column mapping: missing selectedSheet or selectedSheetName'); console.log('Cannot load saved column mapping: missing selectedSheet or selectedSheetName');
return; return;
} }
try { try {
const existingData = localStorage.getItem(RECENT_SHEETS_KEY); const key = await getRecentSheetsKey();
const existingData = localStorage.getItem(key);
if (existingData) { if (existingData) {
const recentSheets = JSON.parse(existingData); const recentSheets = JSON.parse(existingData);
@@ -256,7 +270,7 @@
// Override auto-mapping with saved mapping // Override auto-mapping with saved mapping
mappedIndices = { mappedIndices = {
name: savedSheet.columnMapping.name ?? -1, name: savedSheet.columnMapping.name ?? -1,
nationality: savedSheet.columnMappin.nationality ?? -1, nationality: savedSheet.columnMapping.nationality ?? -1,
birthday: savedSheet.columnMapping.birthday ?? -1, birthday: savedSheet.columnMapping.birthday ?? -1,
pictureUrl: savedSheet.columnMapping.pictureUrl ?? -1, pictureUrl: savedSheet.columnMapping.pictureUrl ?? -1,
alreadyPrinted: savedSheet.columnMapping.alreadyPrinted ?? -1, alreadyPrinted: savedSheet.columnMapping.alreadyPrinted ?? -1,
@@ -318,12 +332,13 @@
}); });
} }
function handleContinue() { async function handleContinue() {
if (!mappingComplete || !$selectedSheet || !selectedSheetName) return; if (!mappingComplete || !$selectedSheet || !selectedSheetName) return;
// Save column mapping to localStorage for the selected sheet // Save column mapping to localStorage for the selected sheet
try { try {
const existingData = localStorage.getItem(RECENT_SHEETS_KEY); const key = await getRecentSheetsKey();
const existingData = localStorage.getItem(key);
let recentSheets = existingData ? JSON.parse(existingData) : []; let recentSheets = existingData ? JSON.parse(existingData) : [];
// Find the current sheet in recent sheets and update its column mapping // Find the current sheet in recent sheets and update its column mapping
@@ -344,9 +359,6 @@
// Update existing entry // Update existing entry
recentSheets[sheetIndex].columnMapping = columnMappingData; recentSheets[sheetIndex].columnMapping = columnMappingData;
recentSheets[sheetIndex].lastUsed = new Date().toISOString(); recentSheets[sheetIndex].lastUsed = new Date().toISOString();
// Ensure we have consistent property names
recentSheets[sheetIndex].id = recentSheets[sheetIndex].id || recentSheets[sheetIndex].id;
} else { } else {
// Add new entry // Add new entry
const newEntry = { const newEntry = {
@@ -364,7 +376,7 @@
} }
} }
localStorage.setItem(RECENT_SHEETS_KEY, JSON.stringify(recentSheets)); localStorage.setItem(key, JSON.stringify(recentSheets));
} catch (err) { } catch (err) {
console.error('Failed to save column mapping to localStorage:', err); console.error('Failed to save column mapping to localStorage:', err);
} }

View File

@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import { selectedSheet, currentStep } from '$lib/stores'; import { selectedSheet, currentStep } from '$lib/stores';
import type { SheetInfoType } from '$lib/stores'; import type { SheetInfoType } from '$lib/stores';
import { searchSheets, ensureToken } from '$lib/google'; import { searchSheets, ensureToken, userEmail } from '$lib/google';
import { hashString } from '$lib/utils';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import Navigator from './subcomponents/Navigator.svelte'; import Navigator from './subcomponents/Navigator.svelte';
@@ -12,7 +13,13 @@
let hasSearched = $state(false); let hasSearched = $state(false);
let recentSheets = $state<any[]>([]); let recentSheets = $state<any[]>([]);
const RECENT_SHEETS_KEY = 'recentSheets'; async function getRecentSheetsKey() {
const email = $userEmail;
if (email) {
return `recentSheets_${await hashString(email)}`;
}
return 'recentSheets_anonymous';
}
onMount(() => { onMount(() => {
ensureToken(); ensureToken();
@@ -37,16 +44,18 @@
} }
} }
function loadRecentSheets() { async function loadRecentSheets() {
try { try {
const saved = localStorage.getItem(RECENT_SHEETS_KEY); const key = await getRecentSheetsKey();
const saved = localStorage.getItem(key);
if (saved) { if (saved) {
recentSheets = JSON.parse(saved); recentSheets = JSON.parse(saved);
} }
} catch (err) { } catch (err) {
console.error('Error loading recent sheets:', err); console.error('Error loading recent sheets:', err);
// If there's an error, clear the stored value // If there's an error, clear the stored value
localStorage.removeItem(RECENT_SHEETS_KEY); const key = await getRecentSheetsKey();
localStorage.removeItem(key);
recentSheets = []; recentSheets = [];
} }
} }

View File

@@ -5,6 +5,7 @@ import { env } from '$env/dynamic/public';
export const accessToken = writable<string | null | undefined>(undefined); export const accessToken = writable<string | null | undefined>(undefined);
export const isSignedIn = writable(false); export const isSignedIn = writable(false);
export const isGoogleApiReady = writable(false); // To track GAPI client readiness export const isGoogleApiReady = writable(false); // To track GAPI client readiness
export const userEmail = writable<string | null>(null);
let tokenClient: google.accounts.oauth2.TokenClient; let tokenClient: google.accounts.oauth2.TokenClient;
let gapiInited = false; let gapiInited = false;
@@ -48,6 +49,27 @@ export function initGoogleClients(callback: () => void) {
} }
} }
/**
* Fetches user's email and stores it.
*/
async function fetchUserInfo(token: string) {
try {
const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
headers: {
Authorization: `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch user info');
}
const profile = await response.json();
userEmail.set(profile.email);
} catch (error) {
console.error('Error fetching user info:', error);
userEmail.set(null);
}
}
// 2. Load GSI script for Auth. This should only be called after GAPI is ready. // 2. Load GSI script for Auth. This should only be called after GAPI is ready.
function initGsiClient(callback: () => void) { function initGsiClient(callback: () => void) {
if (gsiInited) { if (gsiInited) {
@@ -64,7 +86,7 @@ function initGsiClient(callback: () => void) {
tokenClient = google.accounts.oauth2.initTokenClient({ tokenClient = google.accounts.oauth2.initTokenClient({
client_id: env.PUBLIC_GOOGLE_CLIENT_ID, client_id: env.PUBLIC_GOOGLE_CLIENT_ID,
scope: scope:
'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets.readonly', 'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets.readonly https://www.googleapis.com/auth/userinfo.email',
callback: (tokenResponse) => { callback: (tokenResponse) => {
// This callback handles responses from all token requests. // This callback handles responses from all token requests.
if (tokenResponse.error) { if (tokenResponse.error) {
@@ -78,6 +100,7 @@ function initGsiClient(callback: () => void) {
isSignedIn.set(true); isSignedIn.set(true);
// Also set the token for the GAPI client // Also set the token for the GAPI client
if (gapiInited) gapi.client.setToken({ access_token: token }); if (gapiInited) gapi.client.setToken({ access_token: token });
fetchUserInfo(token);
} }
} }
}); });
@@ -152,6 +175,7 @@ export function handleSignOut() {
} }
accessToken.set(null); accessToken.set(null);
isSignedIn.set(false); isSignedIn.set(false);
userEmail.set(null);
console.log('User signed out.'); console.log('User signed out.');
} }

13
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,13 @@
/**
* Hashes a string using the SHA-256 algorithm.
* @param input The string to hash.
* @returns A promise that resolves to the hex-encoded hash string.
*/
export async function hashString(input: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(input);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}