diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..532f804 --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Skopírujte tento súbor ako .env.local a doplňte hodnoty. + +# Supabase (Project Settings → API) +NEXT_PUBLIC_SUPABASE_URL=https://VAS-PROJEKT.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY= +# service_role kľúč – len na serveri (nahrávanie obrázkov), nikdy ho nedávajte do kódu pre prehliadač +SUPABASE_SERVICE_ROLE_KEY= + +# Brevo (SMTP & API → API Keys) +BREVO_API_KEY= +BREVO_SENDER_NAME=COOP Jednota +# E-mail odosielateľa musí byť v Brevo overený +BREVO_SENDER_EMAIL= diff --git a/.gitignore b/.gitignore index 2913495..f4eda9d 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,6 @@ docs/ .vercel .env* + +# vzor premenných prostredia sa commituje +!.env.example diff --git a/README.md b/README.md index e215bc4..f0c1f27 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,66 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# COOP Jednota – generátor newsletterov -## Getting Started +Interný editor e-mailových newsletterov pre COOP Jednota: skladanie kampane z modulov +(hlavička, bannery, produktové sekcie, vernostné zľavy, letáky, recepty, pätička), +editor grafických cenoviek, export do ZIP a odoslanie kampane do Brevo. -First, run the development server: +Technológie: Next.js 16, React 19, Tailwind CSS 4, Supabase (databáza + úložisko obrázkov), Brevo API. + +## Inštalácia na nový server + +### 1. Supabase projekt + +1. Vytvorte nový projekt na [supabase.com](https://supabase.com) (alebo použite vlastný Supabase server). +2. **Databáza:** otvorte *SQL Editor*, vložte celý obsah [`supabase/schema.sql`](supabase/schema.sql) a kliknite *Run*. + Vytvoria sa tabuľky `templates` (kampane) a `badges` (šablóny cenoviek), prístupové pravidlá + a verejné úložisko obrázkov `newsletter-assets`. Skript sa dá bezpečne spustiť aj opakovane. +3. V *Project Settings → API* si skopírujte **Project URL**, **anon** kľúč a **service_role** kľúč. + +### 2. Premenné prostredia ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +cp .env.example .env.local ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +Doplňte do `.env.local` údaje zo Supabase a Brevo (popis je priamo v súbore). +Na hostingu (Vercel, Docker…) nastavte rovnaké premenné v jeho nastaveniach. -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +### 3. Inštalácia a statické obrázky -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +```bash +npm install +npm run db:upload-assets +``` -## Learn More +Druhý príkaz nahrá do úložiska `newsletter-assets` logá, ikony sociálnych sietí, štítky predajní +a ukážkové obrázky zo [`supabase/assets/`](supabase/assets/), ktoré šablóna newslettera používa. -To learn more about Next.js, take a look at the following resources: +### 4. Spustenie -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +```bash +npm run dev # vývoj na http://localhost:3000 +npm run build # produkčný build +npm run start # produkčný server +``` -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +Pri prvom spustení s prázdnou databázou sa vytvorí nová kampaň z predvolenej šablóny +([`src/lib/defaultCampaign.ts`](src/lib/defaultCampaign.ts)) a automaticky sa uloží. -## Deploy on Vercel +## Štruktúra projektu -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +| Cesta | Obsah | +|---|---| +| `src/app/page.tsx` | Hlavná obrazovka editora (stav kampane, auto-save, náhľad) | +| `src/components/editor/` | Panel nastavení, editor cenoviek, modály (import, uložené kampane) | +| `src/lib/emailRenderer.ts` | Generovanie HTML e-mailu z kampane (náhľad aj export) | +| `src/lib/badgeUtils.ts` | Generovanie SVG cenoviek a premenné (`{cena}`, `{zlava}`, `{cena_s_kartou}`…) | +| `src/app/api/` | Serverové API: nahrávanie obrázkov, inline CSS, export a overenie kampane v Brevo | +| `supabase/schema.sql` | Schéma databázy a úložiska | +| `supabase/assets/` | Statické obrázky šablóny (nahrávajú sa cez `npm run db:upload-assets`) | -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +## Poznámky + +- Aplikácia nemá prihlasovanie a databáza je prístupná cez anon kľúč – je určená na interné použitie. + Ak bude verejne dostupná, treba doplniť autentifikáciu. +- Kampane uložené v pôvodnom Supabase projekte obsahujú odkazy na obrázky v pôvodnom úložisku. + Nové kampane a novo nahraté obrázky používajú úložisko z `.env.local`. diff --git a/package.json b/package.json index c732724..a1ef6fe 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "db:upload-assets": "node --env-file=.env.local scripts/upload-assets.mjs" }, "dependencies": { "@supabase/supabase-js": "^2.117.1", diff --git a/scripts/upload-assets.mjs b/scripts/upload-assets.mjs new file mode 100644 index 0000000..d0e93b2 --- /dev/null +++ b/scripts/upload-assets.mjs @@ -0,0 +1,45 @@ +// Nahrá statické obrázky aplikácie (supabase/assets/) do Supabase Storage bucketu newsletter-assets. +// Spustenie: npm run db:upload-assets (číta NEXT_PUBLIC_SUPABASE_URL a SUPABASE_SERVICE_ROLE_KEY z .env.local) +// Existujúce súbory s rovnakým názvom sa prepíšu. +import { createClient } from '@supabase/supabase-js'; +import { readdir, readFile } from 'node:fs/promises'; +import path from 'node:path'; + +const BUCKET = 'newsletter-assets'; +const ASSETS_DIR = path.join(process.cwd(), 'supabase', 'assets'); +const CONTENT_TYPES = { + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.webp': 'image/webp' +}; + +const url = process.env.NEXT_PUBLIC_SUPABASE_URL; +const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY; +if (!url || !serviceKey) { + console.error('Chýba NEXT_PUBLIC_SUPABASE_URL alebo SUPABASE_SERVICE_ROLE_KEY v .env.local'); + process.exit(1); +} + +const supabase = createClient(url, serviceKey); +const files = (await readdir(ASSETS_DIR)).filter((f) => CONTENT_TYPES[path.extname(f).toLowerCase()]); + +let failed = 0; +for (const file of files) { + const body = await readFile(path.join(ASSETS_DIR, file)); + const { error } = await supabase.storage.from(BUCKET).upload(file, body, { + contentType: CONTENT_TYPES[path.extname(file).toLowerCase()], + upsert: true + }); + if (error) { + failed++; + console.error(`✗ ${file}: ${error.message}`); + } else { + console.log(`✓ ${file}`); + } +} + +console.log(`\nNahraté ${files.length - failed}/${files.length} súborov do bucketu ${BUCKET}.`); +if (failed) process.exit(1); diff --git a/src/lib/defaultCampaign.ts b/src/lib/defaultCampaign.ts index fa79ba7..b42abb2 100644 --- a/src/lib/defaultCampaign.ts +++ b/src/lib/defaultCampaign.ts @@ -1,7 +1,8 @@ import { NewsletterCampaign, ProductItem } from '@/types/newsletter'; -export const SUPABASE_ASSETS_BASE = - 'https://dqexxfspkylqttrtxsww.supabase.co/storage/v1/object/public/newsletter-assets/'; +// Verejné úložisko statických obrázkov (logá, ikony, štítky predajní...) v Supabase projekte z .env. +// Súbory sú v supabase/assets/ a nahrajú sa príkazom: npm run db:upload-assets +export const SUPABASE_ASSETS_BASE = `${(process.env.NEXT_PUBLIC_SUPABASE_URL || '').replace(/\/$/, '')}/storage/v1/object/public/newsletter-assets/`; export const DEFAULT_CAMPAIGN: NewsletterCampaign = { id: '', diff --git a/supabase/assets/aktualne_letaky.jpg b/supabase/assets/aktualne_letaky.jpg new file mode 100644 index 0000000..136b2a3 Binary files /dev/null and b/supabase/assets/aktualne_letaky.jpg differ diff --git a/supabase/assets/clanky-casopis_jednota.png b/supabase/assets/clanky-casopis_jednota.png new file mode 100644 index 0000000..bbfbc15 Binary files /dev/null and b/supabase/assets/clanky-casopis_jednota.png differ diff --git a/supabase/assets/clanky-netradicne_recepty.png b/supabase/assets/clanky-netradicne_recepty.png new file mode 100644 index 0000000..823bbdb Binary files /dev/null and b/supabase/assets/clanky-netradicne_recepty.png differ diff --git a/supabase/assets/clanok_barancek.png b/supabase/assets/clanok_barancek.png new file mode 100644 index 0000000..2129243 Binary files /dev/null and b/supabase/assets/clanok_barancek.png differ diff --git a/supabase/assets/clanok_vajicka.png b/supabase/assets/clanok_vajicka.png new file mode 100644 index 0000000..9d53ce2 Binary files /dev/null and b/supabase/assets/clanok_vajicka.png differ diff --git a/supabase/assets/ikona_fb.png b/supabase/assets/ikona_fb.png new file mode 100644 index 0000000..1c9f65d Binary files /dev/null and b/supabase/assets/ikona_fb.png differ diff --git a/supabase/assets/ikona_ig.png b/supabase/assets/ikona_ig.png new file mode 100644 index 0000000..e4b83d5 Binary files /dev/null and b/supabase/assets/ikona_ig.png differ diff --git a/supabase/assets/ikona_tt.png b/supabase/assets/ikona_tt.png new file mode 100644 index 0000000..dc75d2d Binary files /dev/null and b/supabase/assets/ikona_tt.png differ diff --git a/supabase/assets/ikona_yt.png b/supabase/assets/ikona_yt.png new file mode 100644 index 0000000..c5995ba Binary files /dev/null and b/supabase/assets/ikona_yt.png differ diff --git a/supabase/assets/logo-COOP_nadacia.png b/supabase/assets/logo-COOP_nadacia.png new file mode 100644 index 0000000..f29c6f1 Binary files /dev/null and b/supabase/assets/logo-COOP_nadacia.png differ diff --git a/supabase/assets/logo-app_store.png b/supabase/assets/logo-app_store.png new file mode 100644 index 0000000..29043c0 Binary files /dev/null and b/supabase/assets/logo-app_store.png differ diff --git a/supabase/assets/logo-google_play.png b/supabase/assets/logo-google_play.png new file mode 100644 index 0000000..0ef7b65 Binary files /dev/null and b/supabase/assets/logo-google_play.png differ diff --git a/supabase/assets/logo-netradicne_recepty.png b/supabase/assets/logo-netradicne_recepty.png new file mode 100644 index 0000000..6860606 Binary files /dev/null and b/supabase/assets/logo-netradicne_recepty.png differ diff --git a/supabase/assets/logo_coop.png b/supabase/assets/logo_coop.png new file mode 100644 index 0000000..14fdb85 Binary files /dev/null and b/supabase/assets/logo_coop.png differ diff --git a/supabase/assets/nws_podklady-nove_obaly.jpg b/supabase/assets/nws_podklady-nove_obaly.jpg new file mode 100644 index 0000000..c0de395 Binary files /dev/null and b/supabase/assets/nws_podklady-nove_obaly.jpg differ diff --git a/supabase/assets/nws_podklady-velka_noc.jpg b/supabase/assets/nws_podklady-velka_noc.jpg new file mode 100644 index 0000000..70a1a54 Binary files /dev/null and b/supabase/assets/nws_podklady-velka_noc.jpg differ diff --git a/supabase/assets/online_letak.jpg b/supabase/assets/online_letak.jpg new file mode 100644 index 0000000..d61a738 Binary files /dev/null and b/supabase/assets/online_letak.jpg differ diff --git a/supabase/assets/produkt_1.png b/supabase/assets/produkt_1.png new file mode 100644 index 0000000..fde30b8 Binary files /dev/null and b/supabase/assets/produkt_1.png differ diff --git a/supabase/assets/produkt_2.png b/supabase/assets/produkt_2.png new file mode 100644 index 0000000..1cdf2e3 Binary files /dev/null and b/supabase/assets/produkt_2.png differ diff --git a/supabase/assets/produkt_3.png b/supabase/assets/produkt_3.png new file mode 100644 index 0000000..be6557c Binary files /dev/null and b/supabase/assets/produkt_3.png differ diff --git a/supabase/assets/produkt_4.png b/supabase/assets/produkt_4.png new file mode 100644 index 0000000..53d5511 Binary files /dev/null and b/supabase/assets/produkt_4.png differ diff --git a/supabase/assets/produkt_5.png b/supabase/assets/produkt_5.png new file mode 100644 index 0000000..e2e0fec Binary files /dev/null and b/supabase/assets/produkt_5.png differ diff --git a/supabase/assets/produkt_6.png b/supabase/assets/produkt_6.png new file mode 100644 index 0000000..fd6a940 Binary files /dev/null and b/supabase/assets/produkt_6.png differ diff --git a/supabase/assets/stit_potraviny.png b/supabase/assets/stit_potraviny.png new file mode 100644 index 0000000..9a55ebb Binary files /dev/null and b/supabase/assets/stit_potraviny.png differ diff --git a/supabase/assets/stit_supermarket.png b/supabase/assets/stit_supermarket.png new file mode 100644 index 0000000..c1a0d34 Binary files /dev/null and b/supabase/assets/stit_supermarket.png differ diff --git a/supabase/assets/stit_tempo.png b/supabase/assets/stit_tempo.png new file mode 100644 index 0000000..8387f82 Binary files /dev/null and b/supabase/assets/stit_tempo.png differ diff --git a/supabase/assets/vernostna_karta-hlavicka_transparent.png b/supabase/assets/vernostna_karta-hlavicka_transparent.png new file mode 100644 index 0000000..b627aa1 Binary files /dev/null and b/supabase/assets/vernostna_karta-hlavicka_transparent.png differ diff --git a/supabase/assets/vernostna_karta-produkt_1.png b/supabase/assets/vernostna_karta-produkt_1.png new file mode 100644 index 0000000..ce0f1a5 Binary files /dev/null and b/supabase/assets/vernostna_karta-produkt_1.png differ diff --git a/supabase/assets/vernostna_karta-produkt_2.png b/supabase/assets/vernostna_karta-produkt_2.png new file mode 100644 index 0000000..fd5f184 Binary files /dev/null and b/supabase/assets/vernostna_karta-produkt_2.png differ diff --git a/supabase/schema.sql b/supabase/schema.sql new file mode 100644 index 0000000..e7cef79 --- /dev/null +++ b/supabase/schema.sql @@ -0,0 +1,61 @@ +-- Schéma pre Modulárny generátor newsletterov (COOP Jednota) +-- +-- Založenie databázy na novom Supabase projekte: +-- Supabase Dashboard → SQL Editor → vložiť celý tento súbor → Run. +-- Skript je idempotentný – dá sa spustiť opakovane bez straty dát. + +-- 1. Tabuľka šablón / konceptov newslettera +-- data = { preheader, modules[] } – celý obsah kampane ako JSON +CREATE TABLE IF NOT EXISTS templates ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + title TEXT NOT NULL DEFAULT 'Nový newsletter', + status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'ready', 'exported')), + data JSONB NOT NULL DEFAULT '{}'::jsonb, + brevo_campaign_id INTEGER, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- Index pre rýchle zoradenie podľa dátumu úpravy +CREATE INDEX IF NOT EXISTS idx_templates_updated_at ON templates(updated_at DESC); + +-- 2. Tabuľka šablón cenoviek (Badges) +-- layers_config = { layers[], width, align, paddingTop, paddingBottom } +-- (staršie záznamy môžu mať len pole vrstiev – aplikácia podporuje oba tvary) +CREATE TABLE IF NOT EXISTS badges ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + svg_background_url TEXT, + svg_raw TEXT, + layers_config JSONB NOT NULL DEFAULT '[]'::jsonb, + preview_image_url TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- 3. Tabuľka uložených profilov mapovania importu (zatiaľ nevyužitá aplikáciou) +CREATE TABLE IF NOT EXISTS mapping_presets ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + file_type TEXT NOT NULL CHECK (file_type IN ('csv', 'json', 'xml')), + mapping_rules JSONB NOT NULL DEFAULT '{}'::jsonb, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- 4. RLS (Row Level Security) – interný nástroj: povolený prístup cez anon kľúč +ALTER TABLE templates ENABLE ROW LEVEL SECURITY; +ALTER TABLE badges ENABLE ROW LEVEL SECURITY; +ALTER TABLE mapping_presets ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Allow public all on templates" ON templates; +DROP POLICY IF EXISTS "Allow public all on badges" ON badges; +DROP POLICY IF EXISTS "Allow public all on mapping_presets" ON mapping_presets; + +CREATE POLICY "Allow public all on templates" ON templates FOR ALL USING (true) WITH CHECK (true); +CREATE POLICY "Allow public all on badges" ON badges FOR ALL USING (true) WITH CHECK (true); +CREATE POLICY "Allow public all on mapping_presets" ON mapping_presets FOR ALL USING (true) WITH CHECK (true); + +-- 5. Úložisko obrázkov (Storage bucket) – verejne čitateľné, aby sa obrázky zobrazili v e-mailoch. +-- Nahrávanie ide cez /api/upload so service_role kľúčom (obchádza RLS), ďalšie politiky netreba. +INSERT INTO storage.buckets (id, name, public) +VALUES ('newsletter-assets', 'newsletter-assets', true) +ON CONFLICT (id) DO UPDATE SET public = true; diff --git a/supabase_migration.sql b/supabase_migration.sql deleted file mode 100644 index 1b52e9a..0000000 --- a/supabase_migration.sql +++ /dev/null @@ -1,72 +0,0 @@ --- Schéma pre Modulárny generátor newsletterov (COOP Jednota) - --- 1. Tabuľka šablón / konceptov newslettera -CREATE TABLE IF NOT EXISTS templates ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - title TEXT NOT NULL DEFAULT 'Nový newsletter', - status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'ready', 'exported')), - data JSONB NOT NULL DEFAULT '{}'::jsonb, - brevo_campaign_id INTEGER, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - --- Index pre rýchle zoradenie podľa dátumu úpravy -CREATE INDEX IF NOT EXISTS idx_templates_updated_at ON templates(updated_at DESC); - --- 2. Tabuľka cenoviek (Badges) -CREATE TABLE IF NOT EXISTS badges ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name TEXT NOT NULL, - svg_background_url TEXT, - svg_raw TEXT, - layers_config JSONB NOT NULL DEFAULT '[]'::jsonb, - preview_image_url TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - --- 3. Tabuľka uložených profilov mapovania (Data Mapper Presets) -CREATE TABLE IF NOT EXISTS mapping_presets ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name TEXT NOT NULL, - file_type TEXT NOT NULL CHECK (file_type IN ('csv', 'json', 'xml')), - mapping_rules JSONB NOT NULL DEFAULT '{}'::jsonb, - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - --- 4. Východiskové referenčné cenovky (Seed dát pre COOP Jednota) -INSERT INTO badges (name, svg_raw, layers_config) VALUES -( - 'Žltá AKCIA', - '', - '[ - {"id": "l1", "type": "text", "text": "AKCIA", "x": 80, "y": 32, "fontSize": 24, "fontWeight": "900", "fill": "#e0001b", "align": "center"}, - {"id": "l2", "type": "text", "text": "{zlava}", "x": 80, "y": 64, "fontSize": 22, "fontWeight": "800", "fill": "#001a33", "align": "center"} - ]'::jsonb -), -( - 'Červená SUPER CENA', - '', - '[ - {"id": "l1", "type": "text", "text": "SUPER CENA", "x": 80, "y": 34, "fontSize": 18, "fontWeight": "900", "fill": "#ffffff", "align": "center"}, - {"id": "l2", "type": "text", "text": "{zlava}", "x": 80, "y": 64, "fontSize": 24, "fontWeight": "900", "fill": "#ffd100", "align": "center"} - ]'::jsonb -), -( - 'Percentuálna zľava (Kruh)', - '', - '[ - {"id": "l1", "type": "text", "text": "ZĽAVA", "x": 50, "y": 36, "fontSize": 13, "fontWeight": "800", "fill": "#ffffff", "align": "center"}, - {"id": "l2", "type": "text", "text": "{zlava}%", "x": 50, "y": 68, "fontSize": 24, "fontWeight": "900", "fill": "#ffd100", "align": "center"} - ]'::jsonb -) -ON CONFLICT DO NOTHING; - --- 5. Nastavenie RLS (Row Level Security) - pre interný nástroj povolíme verejný prístup cez anon kľúč -ALTER TABLE templates ENABLE ROW LEVEL SECURITY; -ALTER TABLE badges ENABLE ROW LEVEL SECURITY; -ALTER TABLE mapping_presets ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Allow public all on templates" ON templates FOR ALL USING (true) WITH CHECK (true); -CREATE POLICY "Allow public all on badges" ON badges FOR ALL USING (true) WITH CHECK (true); -CREATE POLICY "Allow public all on mapping_presets" ON mapping_presets FOR ALL USING (true) WITH CHECK (true);