Skip to content

Commit 177030d

Browse files
committed
Add QR Code Preset Loader Script to Import Presets.
1 parent 7094dac commit 177030d

File tree

9 files changed

+520
-17
lines changed

9 files changed

+520
-17
lines changed

schemas/qrcode-schema.sql

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-- Create schema for QR code presets system
2+
-- This script creates tables for QR code presets with UUIDs and relationships
3+
4+
-- Enable UUID extension if not already enabled
5+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
6+
7+
-- Create qrcode_presets table
8+
CREATE TABLE IF NOT EXISTS qrcode_presets (
9+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
10+
identifier VARCHAR(255) UNIQUE NOT NULL,
11+
name VARCHAR(255) NOT NULL,
12+
description TEXT,
13+
src TEXT NOT NULL,
14+
height INTEGER NOT NULL,
15+
width INTEGER NOT NULL,
16+
excavate BOOLEAN DEFAULT true,
17+
logo_padding INTEGER,
18+
logo_padding_style VARCHAR(50),
19+
opacity DECIMAL,
20+
quiet_zone INTEGER DEFAULT 0,
21+
remove_qrcode_behind_logo BOOLEAN DEFAULT true,
22+
enabled BOOLEAN DEFAULT true,
23+
cross_origin VARCHAR(50),
24+
x INTEGER,
25+
y INTEGER,
26+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
27+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
28+
);
29+
30+
-- Create indexes for faster lookups
31+
CREATE INDEX IF NOT EXISTS idx_qrcode_presets_identifier ON qrcode_presets(identifier);
32+
CREATE INDEX IF NOT EXISTS idx_qrcode_presets_enabled ON qrcode_presets(enabled);

scripts/load-qrcode-presets.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env ts-node
2+
/**
3+
* QR Code Presets Database Loader
4+
*
5+
* This script loads the QR code presets from the static presets file
6+
* into the Supabase database.
7+
*/
8+
9+
import { createClient } from '@supabase/supabase-js';
10+
import dotenv from 'dotenv';
11+
import { v4 as uuidv4 } from 'uuid';
12+
import { qrcodePresets } from '../src/presets/qrcode.presets';
13+
import type { ImageSettings } from '../src/components/ui/qr-code/qr-code.types';
14+
15+
// Initialize environment variables
16+
dotenv.config();
17+
18+
// Check for required environment variables
19+
const supabaseUrl = process.env.VITE_SUPABASE_URL;
20+
const supabaseKey = process.env.VITE_SUPABASE_ANON_KEY;
21+
22+
if (!supabaseUrl) {
23+
console.error('\x1b[31mError: VITE_SUPABASE_URL environment variable is required\x1b[0m');
24+
console.log('Please make sure you have a .env file with the following variables:');
25+
console.log(' VITE_SUPABASE_URL=your_supabase_url');
26+
console.log(' VITE_SUPABASE_ANON_KEY=your_supabase_anon_key');
27+
process.exit(1);
28+
}
29+
30+
if (!supabaseKey) {
31+
console.error('\x1b[31mError: VITE_SUPABASE_ANON_KEY environment variable is required\x1b[0m');
32+
console.log('Please make sure you have a .env file with the following variables:');
33+
console.log(' VITE_SUPABASE_URL=your_supabase_url');
34+
console.log(' VITE_SUPABASE_ANON_KEY=your_supabase_anon_key');
35+
process.exit(1);
36+
}
37+
38+
console.log('\x1b[32mFound Supabase credentials, connecting to database...\x1b[0m');
39+
40+
// Create Supabase client
41+
const supabase = createClient(supabaseUrl, supabaseKey);
42+
43+
/**
44+
* Insert a QR code preset if it doesn't exist
45+
*/
46+
const insertQRCodePreset = async (identifier: string, preset: ImageSettings): Promise<string | null> => {
47+
try {
48+
// Check if preset already exists
49+
const { data: existingPreset } = await supabase
50+
.from('qrcode_presets')
51+
.select('id, identifier')
52+
.eq('identifier', identifier)
53+
.single();
54+
55+
if (existingPreset) {
56+
console.log(`QR code preset ${identifier} already exists with ID: ${existingPreset.id}`);
57+
return existingPreset.id;
58+
}
59+
60+
// Format preset for database
61+
const presetData = {
62+
identifier,
63+
name: identifier.charAt(0).toUpperCase() + identifier.slice(1).replace(/([A-Z])/g, ' $1').trim(),
64+
description: `${identifier.charAt(0).toUpperCase() + identifier.slice(1).replace(/([A-Z])/g, ' $1').trim()} QR code preset`,
65+
src: preset.src,
66+
height: preset.height,
67+
width: preset.width,
68+
excavate: preset.excavate,
69+
logo_padding: preset.logoPadding,
70+
logo_padding_style: preset.logoPaddingStyle,
71+
opacity: preset.opacity,
72+
quiet_zone: preset.quietZone,
73+
remove_qrcode_behind_logo: preset.removeQrCodeBehindLogo,
74+
enabled: true,
75+
cross_origin: preset.crossOrigin,
76+
x: preset.x,
77+
y: preset.y
78+
};
79+
80+
// Insert QR code preset
81+
const { data, error } = await supabase
82+
.from('qrcode_presets')
83+
.insert(presetData)
84+
.select('id')
85+
.single();
86+
87+
if (error) {
88+
console.error(`Error inserting QR code preset ${identifier}:`, error);
89+
return null;
90+
}
91+
92+
console.log(`QR code preset ${identifier} inserted successfully!`);
93+
return data ? data.id : null;
94+
} catch (error) {
95+
console.error(`Error inserting QR code preset ${identifier}:`, error);
96+
return null;
97+
}
98+
};
99+
100+
/**
101+
* Load all QR code presets into the database
102+
*/
103+
const loadQRCodePresets = async () => {
104+
console.log('\x1b[36mStarting QR code presets database load...\x1b[0m');
105+
106+
try {
107+
// Process all QR code presets in sequence
108+
for (const [id, preset] of Object.entries(qrcodePresets)) {
109+
await insertQRCodePreset(id, preset);
110+
}
111+
112+
console.log('\x1b[32mQR code presets loaded successfully!\x1b[0m');
113+
} catch (error) {
114+
console.error('\x1b[31mError loading QR code presets:\x1b[0m', error);
115+
throw error;
116+
}
117+
};
118+
119+
// Run the loader
120+
loadQRCodePresets().catch(err => console.error('Error loading QR code presets:', err));

src/components/ui/qr-code/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@
4949
* ```
5050
*/
5151

52+
'use client';
53+
5254
// Export all components and types
5355
export * from './qr-code';
5456
export * from './qr-code.types';
55-
56-
// For backwards compatibility
57-
import QRCodeComponent from './qr-code';
58-
export default QRCodeComponent;
57+
export * from './qr-code.hooks';
58+
export { default } from './qr-code';
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
'use client';
2+
3+
import { useEffect, useState } from 'react';
4+
import { ImageSettings } from './qr-code.types';
5+
import { fetchAllQRCodePresets, fetchEnabledQRCodePresets, fetchQRCodePresetByIdentifier } from '@/services/qrcode';
6+
import { logger } from '@/services/logger/logger.service';
7+
8+
/**
9+
* Default QR code preset to use when database fails to load
10+
*/
11+
const defaultQRCodePreset: ImageSettings = {
12+
src: '/images/programmer-icon-transparent.png',
13+
height: 69,
14+
width: 69,
15+
excavate: true,
16+
logoPadding: 13,
17+
logoPaddingStyle: 'circle',
18+
opacity: 1,
19+
quietZone: 0,
20+
removeQrCodeBehindLogo: true
21+
};
22+
23+
/**
24+
* Hook to load QR code presets from the database
25+
*
26+
* @param onlyEnabled - Whether to only load enabled presets
27+
* @returns Object containing the presets and loading state
28+
*/
29+
export const useQRCodePresets = (onlyEnabled = false) => {
30+
const [presets, setPresets] = useState<Record<string, ImageSettings>>({ programmerIcon: defaultQRCodePreset });
31+
const [isLoading, setIsLoading] = useState<boolean>(true);
32+
const [error, setError] = useState<Error | null>(null);
33+
34+
useEffect(() => {
35+
const loadQRCodePresets = async () => {
36+
try {
37+
setIsLoading(true);
38+
39+
// Fetch presets from database
40+
const dbPresets = onlyEnabled
41+
? await fetchEnabledQRCodePresets()
42+
: await fetchAllQRCodePresets();
43+
44+
// If no presets found, ensure we have at least a default one
45+
if (Object.keys(dbPresets).length === 0) {
46+
logger.warn('No QR code presets found in database, using default fallback');
47+
setPresets({ programmerIcon: defaultQRCodePreset });
48+
} else {
49+
// Update state with database presets
50+
setPresets(dbPresets);
51+
}
52+
53+
setError(null);
54+
} catch (err) {
55+
logger.error('Failed to load QR code presets from database:', err);
56+
setError(err instanceof Error ? err : new Error('Failed to load QR code presets'));
57+
58+
// Use minimal default preset as fallback
59+
setPresets({ programmerIcon: defaultQRCodePreset });
60+
} finally {
61+
setIsLoading(false);
62+
}
63+
};
64+
65+
loadQRCodePresets();
66+
}, [onlyEnabled]);
67+
68+
return { presets, isLoading, error };
69+
};
70+
71+
/**
72+
* Hook to get a specific QR code preset by identifier
73+
*
74+
* @param identifier - The preset identifier to fetch
75+
* @returns The preset and loading state
76+
*/
77+
export const useQRCodePreset = (identifier: string) => {
78+
const [preset, setPreset] = useState<ImageSettings | null>(null);
79+
const [isLoading, setIsLoading] = useState<boolean>(true);
80+
const [error, setError] = useState<Error | null>(null);
81+
82+
useEffect(() => {
83+
const loadQRCodePreset = async () => {
84+
try {
85+
setIsLoading(true);
86+
87+
// Fetch preset from database
88+
const dbPreset = await fetchQRCodePresetByIdentifier(identifier);
89+
90+
if (!dbPreset) {
91+
logger.warn(`QR code preset ${identifier} not found, using default fallback`);
92+
setPreset(defaultQRCodePreset);
93+
} else {
94+
setPreset(dbPreset);
95+
}
96+
97+
setError(null);
98+
} catch (err) {
99+
logger.error(`Failed to load QR code preset ${identifier}:`, err);
100+
setError(err instanceof Error ? err : new Error(`Failed to load QR code preset ${identifier}`));
101+
102+
// Use default preset as fallback
103+
setPreset(defaultQRCodePreset);
104+
} finally {
105+
setIsLoading(false);
106+
}
107+
};
108+
109+
loadQRCodePreset();
110+
}, [identifier]);
111+
112+
return { preset, isLoading, error };
113+
};

src/components/ui/qr-code/qr-code.module.css

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,39 @@
99
margin-bottom: 1rem;
1010
}
1111

12+
/* Loading state for the container */
13+
.loading {
14+
position: relative;
15+
}
16+
17+
/* Loading indicator */
18+
.loadingIndicator {
19+
width: 40px;
20+
height: 40px;
21+
border: 3px solid rgba(255, 255, 255, 0.2);
22+
border-top-color: rgba(255, 255, 255, 0.8);
23+
border-radius: 50%;
24+
animation: qrcode-loading-spinner 0.8s linear infinite;
25+
position: absolute;
26+
top: 50%;
27+
left: 50%;
28+
transform: translate(-50%, -50%);
29+
}
30+
31+
/* Loading animation */
32+
@keyframes qrcode-loading-spinner {
33+
to {
34+
transform: translate(-50%, -50%) rotate(360deg);
35+
}
36+
}
37+
1238
/* Wrapper for the QR code image */
1339
.qr-wrapper {
1440
padding: 0.5rem;
1541
border-radius: 0.25rem;
42+
position: relative;
43+
min-height: 100px;
44+
min-width: 100px;
1645
}
1746

1847
/* Title text below QR code */

0 commit comments

Comments
 (0)