@rayels/loi25 1.0.0

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.
Files changed (2) hide show
  1. package/index.js +176 -0
  2. package/package.json +35 -0
package/index.js ADDED
@@ -0,0 +1,176 @@
1
+ /**
2
+ * @rayels/loi25
3
+ * React Cookie Consent Banner — Loi 25 Quebec Compliant
4
+ * https://rayelsconsulting.com
5
+ */
6
+
7
+ import React, { useState, useEffect } from 'react';
8
+
9
+ const STORAGE_KEY = 'loi25-consent';
10
+ const STORAGE_TIMESTAMP_KEY = 'loi25-consent-date';
11
+
12
+ const TEXTS = {
13
+ fr: {
14
+ title: 'Respect de votre vie privee',
15
+ message: 'Ce site utilise des cookies pour ameliorer votre experience. Conformement a la Loi 25 du Quebec, nous demandons votre consentement.',
16
+ acceptAll: 'Tout accepter',
17
+ acceptNecessary: 'Necessaires seulement',
18
+ privacyPolicy: 'Politique de confidentialite',
19
+ poweredBy: 'Propulse par',
20
+ },
21
+ en: {
22
+ title: 'Your Privacy Matters',
23
+ message: 'This website uses cookies to improve your experience. In compliance with Quebec\'s Law 25, we ask for your consent.',
24
+ acceptAll: 'Accept All',
25
+ acceptNecessary: 'Necessary Only',
26
+ privacyPolicy: 'Privacy Policy',
27
+ poweredBy: 'Powered by',
28
+ },
29
+ };
30
+
31
+ export function getConsent() {
32
+ try {
33
+ return localStorage.getItem(STORAGE_KEY);
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ export function isAnalyticsAllowed() {
40
+ return getConsent() === 'all';
41
+ }
42
+
43
+ export function revokeConsent() {
44
+ try {
45
+ localStorage.removeItem(STORAGE_KEY);
46
+ localStorage.removeItem(STORAGE_TIMESTAMP_KEY);
47
+ } catch {}
48
+ }
49
+
50
+ export function Loi25Banner({
51
+ lang = 'fr',
52
+ position = 'bottom',
53
+ theme = 'light',
54
+ privacyPolicyUrl = '/politique-de-confidentialite',
55
+ poweredByLink = true,
56
+ onAcceptAll,
57
+ onAcceptNecessary,
58
+ }) {
59
+ const [visible, setVisible] = useState(false);
60
+ const texts = TEXTS[lang] || TEXTS.fr;
61
+ const isDark = theme === 'dark';
62
+
63
+ useEffect(() => {
64
+ if (!getConsent()) setVisible(true);
65
+ }, []);
66
+
67
+ const handleAcceptAll = () => {
68
+ try {
69
+ localStorage.setItem(STORAGE_KEY, 'all');
70
+ localStorage.setItem(STORAGE_TIMESTAMP_KEY, String(Date.now()));
71
+ } catch {}
72
+ setVisible(false);
73
+ if (onAcceptAll) onAcceptAll();
74
+ };
75
+
76
+ const handleAcceptNecessary = () => {
77
+ try {
78
+ localStorage.setItem(STORAGE_KEY, 'necessary');
79
+ localStorage.setItem(STORAGE_TIMESTAMP_KEY, String(Date.now()));
80
+ } catch {}
81
+ setVisible(false);
82
+ if (onAcceptNecessary) onAcceptNecessary();
83
+ };
84
+
85
+ if (!visible) return null;
86
+
87
+ return (
88
+ <div
89
+ style={{
90
+ position: 'fixed',
91
+ left: 0,
92
+ right: 0,
93
+ [position]: 0,
94
+ zIndex: 999999,
95
+ background: isDark ? '#18181b' : '#ffffff',
96
+ borderTop: position === 'bottom' ? `1px solid ${isDark ? '#27272a' : '#e2e8f0'}` : 'none',
97
+ borderBottom: position === 'top' ? `1px solid ${isDark ? '#27272a' : '#e2e8f0'}` : 'none',
98
+ padding: '20px 24px',
99
+ fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
100
+ fontSize: '14px',
101
+ color: isDark ? '#e4e4e7' : '#1e293b',
102
+ boxShadow: '0 -2px 20px rgba(0,0,0,0.08)',
103
+ }}
104
+ >
105
+ <div style={{ maxWidth: 960, margin: '0 auto' }}>
106
+ <div style={{ fontWeight: 700, fontSize: 16, marginBottom: 8 }}>
107
+ {texts.title}
108
+ </div>
109
+ <p style={{ margin: '0 0 16px', color: isDark ? '#a1a1aa' : '#64748b', lineHeight: 1.5 }}>
110
+ {texts.message}
111
+ </p>
112
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
113
+ <button
114
+ onClick={handleAcceptAll}
115
+ style={{
116
+ background: '#1d4ed8',
117
+ color: '#fff',
118
+ border: 'none',
119
+ padding: '10px 20px',
120
+ borderRadius: 8,
121
+ fontWeight: 600,
122
+ fontSize: 14,
123
+ cursor: 'pointer',
124
+ }}
125
+ >
126
+ {texts.acceptAll}
127
+ </button>
128
+ <button
129
+ onClick={handleAcceptNecessary}
130
+ style={{
131
+ background: isDark ? '#27272a' : '#f1f5f9',
132
+ color: isDark ? '#e4e4e7' : '#334155',
133
+ border: `1px solid ${isDark ? '#27272a' : '#e2e8f0'}`,
134
+ padding: '10px 20px',
135
+ borderRadius: 8,
136
+ fontWeight: 600,
137
+ fontSize: 14,
138
+ cursor: 'pointer',
139
+ }}
140
+ >
141
+ {texts.acceptNecessary}
142
+ </button>
143
+ <a
144
+ href={privacyPolicyUrl}
145
+ style={{
146
+ color: isDark ? '#a1a1aa' : '#64748b',
147
+ fontSize: 12,
148
+ textDecoration: 'underline',
149
+ marginLeft: 8,
150
+ }}
151
+ >
152
+ {texts.privacyPolicy}
153
+ </a>
154
+ {poweredByLink && (
155
+ <a
156
+ href="https://rayelsconsulting.com"
157
+ target="_blank"
158
+ rel="noopener noreferrer"
159
+ style={{
160
+ color: isDark ? '#a1a1aa' : '#64748b',
161
+ fontSize: 11,
162
+ marginLeft: 'auto',
163
+ textDecoration: 'none',
164
+ opacity: 0.7,
165
+ }}
166
+ >
167
+ {texts.poweredBy} Rayels
168
+ </a>
169
+ )}
170
+ </div>
171
+ </div>
172
+ </div>
173
+ );
174
+ }
175
+
176
+ export default Loi25Banner;
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@rayels/loi25",
3
+ "version": "1.0.0",
4
+ "description": "React component for Loi 25 Quebec cookie consent banner. Bilingual, lightweight, compliant. By Rayels Consulting.",
5
+ "main": "index.js",
6
+ "peerDependencies": {
7
+ "react": ">=17.0.0",
8
+ "react-dom": ">=17.0.0"
9
+ },
10
+ "keywords": [
11
+ "react",
12
+ "loi 25",
13
+ "loi25",
14
+ "quebec",
15
+ "cookie consent",
16
+ "privacy",
17
+ "cookie banner",
18
+ "gdpr",
19
+ "conformite",
20
+ "quebec privacy law",
21
+ "bill 64",
22
+ "react component"
23
+ ],
24
+ "author": {
25
+ "name": "Rayels Consulting",
26
+ "email": "contact@rayelsconsulting.com",
27
+ "url": "https://rayelsconsulting.com"
28
+ },
29
+ "homepage": "https://rayelsconsulting.com",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/raracx/rayels-loi25-quebec"
33
+ },
34
+ "license": "MIT"
35
+ }