Files
S. EsmaeiliandClaude Opus 5.5 789d224ea0 feat: monorepo with NestJS API, admin panel and DB-driven website
Restructure the repo into a pnpm + Turborepo monorepo:

- apps/web: Nuxt 4 site, now server-rendered and reading all content
  from the API; pages cached with SWR and purged on content changes
- apps/api: NestJS 12 + Prisma 7 + PostgreSQL. Auth with short-lived
  access tokens and rotating httpOnly refresh cookies, roles (ADMIN,
  EDITOR), catalog, editable site sections, media upload to S3-compatible
  storage (converted to WebP), contact messages, audit log
- apps/admin: Vue 3 admin panel (Ant Design Vue, Pinia, Tailwind) for
  products, categories, site sections, messages, media and users
- packages/shared: Zod schemas and types shared by API and admin

Also:
- seed script that loads the original 8 categories / 89 products and
  section content
- Docker images for api (with a one-off migrate service), web, admin,
  and an nginx gateway for both domains
- dev and production compose files, daily database backup service
- API e2e tests

Removes the committed static out/ build and the old root Dockerfile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:31:05 +03:30

119 lines
3.4 KiB
CSS

/*
* Ant Design Vue ↔ project look.
* antd's CSS-in-JS is unlayered while Tailwind v4 lives in cascade layers, so Tailwind classes
* can't restyle antd elements. These unlayered rules pin antd form controls to the exact
* pre-migration styling (same values as the old Tailwind `inputCls` / submit button).
*/
/* ---- form shell ---- */
.contact-form.ant-form {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
/* antd resets items and their grid wrappers to 14px/1.5714; inherit like the original markup did
(the control line box decides the gap under the inline-block textarea) */
.contact-form .ant-form-item,
.contact-form .ant-form-item .ant-row,
.contact-form .ant-form-item .ant-col {
margin-bottom: 0;
font-size: inherit;
line-height: inherit;
}
.contact-form .ant-form-item .ant-form-item-label {
padding: 0 0 8px; /* mb-2 */
line-height: 16px;
}
.contact-form .ant-form-item-label > label {
width: 100%; /* original labels were full-width blocks */
height: auto;
font-size: 12px; /* text-xs */
line-height: 16px;
color: var(--color-muted);
}
.contact-form .ant-form-item-explain {
font-size: 12px;
line-height: 20px;
padding-top: 4px;
}
/* ---- inputs (= w-full rounded-lg border border-line bg-bg px-4 py-3.5 text-sm text-ink) ---- */
.contact-form .ant-input {
width: 100%;
border-radius: 8px;
border: 1px solid var(--color-line);
background-color: var(--color-bg);
padding: 14px 16px;
font-family: inherit;
font-size: 14px;
line-height: 20px;
color: var(--color-ink);
box-shadow: none;
transition-property: color, background-color, border-color;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.contact-form .ant-input::placeholder {
color: color-mix(in oklab, var(--color-muted) 50%, transparent);
}
.contact-form .ant-input:hover {
border-color: var(--color-line);
}
.contact-form .ant-input:focus,
.contact-form .ant-input-focused {
border-color: var(--color-accent);
outline: none;
box-shadow: none;
}
/* antd's .ant-input-rtl forces direction:rtl; honour an explicit dir="ltr" (phone field) */
.contact-form .ant-input[dir="ltr"] {
direction: ltr;
text-align: left;
}
.contact-form textarea.ant-input {
resize: none;
min-height: 0;
vertical-align: baseline; /* native inline-block textarea alignment (antd sets bottom) */
}
/* ---- submit (= rounded-full bg-accent py-4 font-bold text-bg, hover:bg-accent-soft + glow) ---- */
.contact-form .ant-btn.contact-submit {
display: flex;
width: 100%;
height: auto;
align-items: center;
justify-content: center;
gap: 10px;
border: 0;
border-radius: calc(infinity * 1px); /* = tailwind rounded-full */
background-color: var(--color-accent);
padding: 16px 0;
font-family: inherit;
font-size: 16px;
line-height: 24px;
font-weight: 700;
color: var(--color-bg);
box-shadow: none;
cursor: default;
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
.contact-form .ant-btn.contact-submit > span {
display: inline;
}
@media (hover: hover) {
.contact-form .ant-btn.contact-submit:hover {
background-color: var(--color-accent-soft);
color: var(--color-bg);
box-shadow: 0 8px 45px rgba(255, 90, 0, 0.45);
}
}
.contact-form .ant-btn.contact-submit:disabled {
opacity: 0.6;
border: 0;
}
/* no click-wave ring (wasn't part of the original button) */
.contact-form .ant-btn.contact-submit::after,
.contact-form .ant-wave {
display: none;
}