afnon-website1

Afnon

Website and staff panel for Afnon, a restaurant on Qatortol Street, Tashkent.

Built with Next.js 14 (App Router), TypeScript, Tailwind CSS, Framer Motion and Supabase.

Getting started

npm install
cp .env.local.example .env.local   # then fill in the values
npm run dev                        # http://localhost:3000
Variable Where it’s used Notes
NEXT_PUBLIC_SUPABASE_URL server Supabase project URL
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY or NEXT_PUBLIC_SUPABASE_ANON_KEY server Public key. Used only for public reads and for inserting visitor messages. Either name works.
SUPABASE_SERVICE_ROLE_KEY server only Every staff read and write, signed upload URLs, and the message rate-limit check. Never add a NEXT_PUBLIC_ prefix.
ADMIN_PASSWORD server only The shared staff password for /admin.

Set the same variables in your host’s settings (for example Vercel → Project → Settings → Environment Variables).

Scripts

Command Does
npm run dev Development server
npm run build Production build
npm start Serve the production build
npm run lint ESLint
npm run typecheck TypeScript, no emit

Where to change things

What File
Every word visitors read, in all three languages (hero, story, labels, form messages, hours, alt text) lib/i18n.ts
Category name translations categoryTranslations in lib/i18n.ts
Telegram username, phone, WhatsApp, email, social links, map address, photography lib/site.ts
Colours, type scale, spacing app/globals.css and tailwind.config.ts
Dishes, dish photos, films, messages Staff panel at /admin — no code

Placeholders to replace before launch (all marked in those files): phone, Telegram username, WhatsApp, email, social links, opening hours, building number and postcode, stock photography, and the story’s narrative copy (not verified facts). Also confirm the Russian spelling of the street, “улица Катартал”.

Languages

Contact form and inbox

Ordering via Telegram

Each available dish has an “Order via Telegram” link to https://t.me/<username>?text=…. The message is prefilled in the visitor’s language with the dish name as shown. The username is set once, as contact.telegramUsername in lib/site.ts. There is no cart or checkout by design.

Admin security

Freshness

The home page renders per request (it depends on the language cookie), but Supabase reads stay in Next’s data cache for up to 5 minutes. Every staff change clears that cache immediately, so edits appear without a redeploy.

Project structure

app/
  page.tsx, layout.tsx, not-found.tsx, globals.css
  actions/locale.ts          set the language cookie
  actions/contact.ts         receive a visitor's message
  admin/actions.ts           login, logout, dish CRUD, ordering
  admin/media-actions.ts     photo + film uploads, order, show/hide, delete
  admin/inbox-actions.ts     read / unread
  admin/(dashboard)/         guard + shell, menu, videos, inbox pages
components/
  site/                      header, language switcher, hero, story, menu (+ photo viewer), films, gallery, location, reserve, contact form, footer
  admin/                     menu manager, dish row, dish form, photo manager, film manager, toaster, shared action helpers
  ui/                        Action, ArchFrame, SectionHeading, Reveal, motion/ (MaskedWords, RuleDraw, ImageReveal, Parallax)
lib/
  i18n.ts                    all copy + fallback + category translations
  site.ts                    business facts + photography
  menu.ts, menu-format.ts    localized menu read, price and slug formatting
  ordering.ts                shared ordering rules (public and admin agree)
  videos.ts, admin-data.ts   reads
  media.ts, storage.ts       upload rules, byte sniffing, storage helpers
  contact.ts, antispam.ts    form validation and abuse protection
  telegram.ts                safe t.me link building
  auth.ts, admin.ts          session and the admin guard
  supabase.ts, supabase-admin.ts