12 Commits

Author SHA1 Message Date
jeanGaston abd3daea7e chore: bump version to 1.0.3
Publish Docker image / build-and-push (push) Successful in 4m23s
2026-07-30 17:35:59 +02:00
jeanGaston 0da6dc7810 fix(prisma): track migration SQL files in git
migration.sql was gitignored, so CI builds a Docker image from a
fresh clone with empty migration directories. prisma migrate deploy
then has nothing to apply, leaving the database schema-less on first
launch (P2021: table does not exist).
2026-07-30 17:32:40 +02:00
jeanGaston d8d152e249 chore: bump version to 1.0.2
Publish Docker image / build-and-push (push) Successful in 4m46s
2026-07-30 16:40:56 +02:00
jeanGaston 75ea9b7416 fix(docker): fix data volume ownership on fresh bind mounts
/app/data is a bind mount, so the chown baked into the image is
overwritten by the host directory's ownership at runtime. On a fresh
prod deploy Docker creates ./data as root-owned, but the container ran
as the non-root nextjs user, so Prisma couldn't open lovelope.db
("unable to open database file").

Add an entrypoint that starts as root, chowns /app/data, then drops to
nextjs via su-exec before running migrations and starting the server.
2026-07-30 16:40:45 +02:00
jeanGaston cd36fc5723 docs: document the dev/tag release process in GIT_WORKFLOW.md
Spells out the two-channel CI trigger (dev branch for test builds, v*
tags for releases) as an actual step-by-step process: keeping dev in
sync with main/master, bumping package.json, tagging, and the
required Gitea repo secrets/variables.
2026-07-30 16:35:45 +02:00
jeanGaston 3987a4e43e chore: bump version to 1.0.1
Publish Docker image / build-and-push (push) Successful in 3m22s
2026-07-30 15:47:12 +02:00
jeanGaston 5ca66f3ed7 chore: rename database file from askedout.db to lovelope.db
Publish Docker image / build-and-push (push) Successful in 3m34s
Leftover naming from before the app was renamed to lovelope. Renamed
the local data/askedout.db file to data/lovelope.db to match (data/
is gitignored, so this doesn't touch version control).
2026-07-30 15:46:21 +02:00
jeanGaston c36d146fef chore: bump version to 1.0.0 for first release
Publish Docker image / build-and-push (push) Successful in 3m39s
2026-07-30 15:28:37 +02:00
jeanGaston 3ac68a88d0 docs: lead Quick start with the published GHCR image
Publish Docker image / build-and-push (push) Successful in 3m31s
Running the app no longer requires cloning the repo: pull the
published ghcr.io image directly via docker-compose. Move the
build-from-source flow (existing docker-compose.yml + Dockerfile) to
a secondary section for contributors.
2026-07-30 12:03:29 +02:00
jeanGaston 94b02198dd ci: build images from a dev branch and release tags, not every commit
Every push to master was triggering a build+push, since that's where
day-to-day commits land. Switch to two deliberate channels instead:
pushing to dev produces a rolling :dev tag for testing, and pushing a
v* tag produces :latest + the version tag for real releases. Plain
commits/merges to master/main no longer build anything on their own.
2026-07-30 12:03:20 +02:00
jeanGaston b6c769eecc chore: remove unused shadcn ui components and their Radix deps
Publish Docker image / build-and-push (push) Successful in 4m14s
badge.tsx, dialog.tsx, and separator.tsx were never imported anywhere
in the app. Drop them along with @radix-ui/react-dialog and
@radix-ui/react-separator, which had no other consumers.
2026-07-30 11:50:47 +02:00
jeanGaston bf8fef9190 ci: push to GHCR from the Gitea workflow instead of a separate one
The Gitea instance's act_runner is the CI that actually executes here;
GitHub Actions on the mirror isn't confirmed to run at all (and a
push-mirror lacking the "workflow" PAT scope can silently drop
.github/workflows changes anyway). Add a GHCR login/push to the
existing .gitea/workflows/docker-publish.yml job instead, so one
build produces tags on both the Gitea registry and ghcr.io. Drop the
now-redundant .github/workflows/docker-publish.yml.

Requires a new repo secret GHCR_TOKEN: a GitHub PAT with write:packages
scope (separate from REGISTRY_TOKEN, which is scoped to the Gitea
registry).
2026-07-30 11:50:38 +02:00
16 changed files with 206 additions and 274 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# SQLite database file (mounted on a persistent volume in Docker)
DATABASE_URL="file:/app/data/askedout.db"
DATABASE_URL="file:/app/data/lovelope.db"
# Public base URL (used in QR codes and share links)
APP_URL="http://localhost:3000"
+31 -4
View File
@@ -1,8 +1,12 @@
name: Publish Docker image
# Two channels, not one-image-per-commit:
# - push to `dev` -> rolling `:dev` tag, for testing in-progress work
# - push tag `v*` -> `:latest` + the version tag, for actual releases
# Plain commits/merges to master/main build nothing on their own.
on:
push:
branches: [main, master]
branches: [dev]
tags: ['v*']
permissions:
@@ -20,6 +24,24 @@ jobs:
- name: Compute lowercase image name
run: echo "IMAGE_NAME=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Compute image tags
run: |
if [ "${{ github.ref_type }}" = "tag" ]; then
VERSION="${{ github.ref_name }}"
TAGS="${{ vars.REGISTRY_HOST }}/${IMAGE_NAME}:latest
${{ vars.REGISTRY_HOST }}/${IMAGE_NAME}:${VERSION}
ghcr.io/${IMAGE_NAME}:latest
ghcr.io/${IMAGE_NAME}:${VERSION}"
else
TAGS="${{ vars.REGISTRY_HOST }}/${IMAGE_NAME}:dev
ghcr.io/${IMAGE_NAME}:dev"
fi
{
echo "TAGS<<TAGS_EOF"
echo "$TAGS"
echo "TAGS_EOF"
} >> "$GITHUB_ENV"
- name: Log in to Gitea container registry
uses: docker/login-action@v3
with:
@@ -27,11 +49,16 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ vars.REGISTRY_HOST }}/${{ env.IMAGE_NAME }}:latest
${{ vars.REGISTRY_HOST }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
tags: ${{ env.TAGS }}
-37
View File
@@ -1,37 +0,0 @@
name: Publish Docker image
on:
push:
branches: [main, master]
tags: ['v*']
permissions:
contents: read
packages: write
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Compute lowercase image name
run: echo "IMAGE_NAME=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/${{ env.IMAGE_NAME }}:latest
ghcr.io/${{ env.IMAGE_NAME }}:${{ github.sha }}
-3
View File
@@ -38,8 +38,5 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts
# prisma
prisma/migrations/*/migration.sql
# local Claude Code tooling (skills, agent config) - not part of the app
/.claude
+4 -4
View File
@@ -28,7 +28,7 @@ RUN npm run build
# ── Stage 3: runner ────────────────────────────────────────────────────────────
FROM node:20-alpine AS runner
RUN apk add --no-cache libc6-compat openssl
RUN apk add --no-cache libc6-compat openssl su-exec
WORKDIR /app
ENV NODE_ENV=production
@@ -48,11 +48,11 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
RUN mkdir -p /app/data && chown nextjs:nodejs /app/data
USER nextjs
COPY --chmod=755 docker-entrypoint.sh ./docker-entrypoint.sh
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
# Run migrations then start the server
CMD ["sh", "-c", "npx prisma migrate deploy && node server.js"]
# Fix ownership of the bind-mounted /app/data volume, then drop to nextjs
ENTRYPOINT ["./docker-entrypoint.sh"]
+61 -3
View File
@@ -46,10 +46,68 @@ Types: `feat`, `fix`, `refactor`, `style`, `docs`, `chore`, `test`.
- Never force-push `main`. Force-pushing a feature branch you own (after a
rebase) is fine; never force-push a branch someone else might be using.
## Tags / releases
## Releases & Docker images
Tag production releases as `v<major>.<minor>.<patch>` (semver) once this
project has a deployment cadence worth marking. Not required for every merge.
CI (`.gitea/workflows/docker-publish.yml`) never builds an image from a
plain commit or merge to `main`/`master` — only from two deliberate
triggers, each producing tags on both the Gitea registry and GHCR:
| Trigger | Produces | Purpose |
|------------------|--------------------------|-------------------------------------|
| push to `dev` | `:dev` | test a build before releasing |
| push a `v*` tag | `:latest` + `:vX.Y.Z` | ship an actual release |
### Testing a build (`dev`)
`dev` is a disposable pointer, not a branch with history of its own — it
should always match `main`/`master` exactly. Bring it up to date and push
whenever you want a fresh test image:
```
git checkout dev
git merge main --ff-only
git push origin dev
```
If `dev` has drifted and won't fast-forward (e.g. after a rebase, or a
commit landed only on `dev` by mistake), reset it instead of merging —
`dev` has no unique history worth preserving:
```
git checkout dev
git reset --hard main
git push origin dev --force-with-lease
```
### Shipping a release (tag)
1. Bump the version in `package.json` on `main`/`master` (a plain commit —
builds nothing on its own):
```
# edit package.json: "version": "X.Y.Z"
git add package.json
git commit -m "chore: bump version to X.Y.Z"
git push origin main
```
2. Tag that commit and push the tag — this is what actually triggers the
release build:
```
git tag vX.Y.Z
git push origin vX.Y.Z
```
3. Never move or re-push an existing tag once it's out (it may already be
pulled/deployed). If you need to fix something after tagging, bump to
the next patch version and tag again instead.
Use `v<major>.<minor>.<patch>` (semver) for tags.
### Required repo secrets/variables (Gitea → Settings → Actions)
- `vars.REGISTRY_HOST` — the Gitea instance hostname (no protocol), used to
tag/push to the built-in Gitea container registry.
- `secrets.REGISTRY_TOKEN` — a Gitea PAT with `write:package` scope.
- `secrets.GHCR_TOKEN` — a GitHub PAT with `write:packages` scope, used to
also push to `ghcr.io`.
## What not to commit
+36 -5
View File
@@ -10,7 +10,40 @@ No accounts, no friction, just a sweet little link. Everything personal is encry
## Quick start
You need [Docker](https://www.docker.com/) installed. That's it.
You need [Docker](https://www.docker.com/) installed. That's it — no clone required, the image is published on GHCR.
Create a `docker-compose.yml`:
```yaml
services:
app:
image: ghcr.io/jeangaston/lovelope:latest
ports:
- "3000:3000"
environment:
DATABASE_URL: "file:/app/data/lovelope.db"
APP_URL: "http://localhost:3000"
GIPHY_API_KEY: "${GIPHY_API_KEY:-}"
volumes:
- ./data:/app/data
restart: unless-stopped
```
Then:
```bash
docker compose up -d
```
Open **http://localhost:3000**. Your data lives in `./data/lovelope.db` on your machine.
Want GIF search to work? Grab a free key at [developers.giphy.com](https://developers.giphy.com/), export it as `GIPHY_API_KEY` before running `docker compose up` (or drop it in a `.env` file next to `docker-compose.yml`), then restart. Without it, the app still runs fine, GIF search is just disabled.
Pin a specific version instead of always tracking `latest` by using a release tag, e.g. `ghcr.io/jeangaston/lovelope:v1.0.0`.
### Build from source
If you'd rather build the image yourself instead of pulling from GHCR:
```bash
git clone https://github.com/jeangaston/lovelope.git && cd lovelope
@@ -18,9 +51,7 @@ cp .env.example .env
docker compose up --build
```
Open **http://localhost:3000**. Everything (the app and its database) runs in one container; your data lives in `./data/askedout.db` on your machine.
Want GIF search to work? Grab a free key at [developers.giphy.com](https://developers.giphy.com/) and put it in `.env` as `GIPHY_API_KEY`, then restart. Without it, the app still runs fine, GIF search is just disabled.
This uses the [docker-compose.yml](docker-compose.yml) in the repo, which builds from the local [Dockerfile](Dockerfile) instead of pulling a published image.
## Features
@@ -52,7 +83,7 @@ Browsers never send the part after `#` to any server, so that key never reaches
| Variable | Required | Description |
|---|---|---|
| `DATABASE_URL` | ✅ | SQLite file path, default `file:/app/data/askedout.db` works out of the box with Docker |
| `DATABASE_URL` | ✅ | SQLite file path, default `file:/app/data/lovelope.db` works out of the box with Docker |
| `APP_URL` | ✅ | Base URL used to build share links, e.g. `http://localhost:3000` |
| `GIPHY_API_KEY` | optional | Enables GIF search. Free key at [developers.giphy.com](https://developers.giphy.com/), leave blank to disable |
+1 -1
View File
@@ -6,7 +6,7 @@ services:
ports:
- "3000:3000"
environment:
DATABASE_URL: "file:/app/data/askedout.db"
DATABASE_URL: "file:/app/data/lovelope.db"
APP_URL: "http://localhost:3000"
NODE_ENV: "development"
GIPHY_API_KEY: "${GIPHY_API_KEY:-}"
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
set -e
chown -R nextjs:nodejs /app/data
exec su-exec nextjs sh -c "npx prisma migrate deploy && node server.js"
+1 -25
View File
@@ -7,12 +7,11 @@
"": {
"name": "lovelope",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"@prisma/client": "^5.15.0",
"@radix-ui/react-alert-dialog": "^1.1.23",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-label": "^2.1.15",
"@radix-ui/react-separator": "^1.1.15",
"@radix-ui/react-slot": "^1.3.3",
"@radix-ui/react-switch": "^1.3.7",
"@radix-ui/react-tabs": "^1.1.21",
@@ -1358,29 +1357,6 @@
}
}
},
"node_modules/@radix-ui/react-separator": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz",
"integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.10"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz",
+1 -3
View File
@@ -1,6 +1,6 @@
{
"name": "lovelope",
"version": "0.1.0",
"version": "1.0.3",
"private": true,
"license": "MIT",
"scripts": {
@@ -17,9 +17,7 @@
"dependencies": {
"@prisma/client": "^5.15.0",
"@radix-ui/react-alert-dialog": "^1.1.23",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-label": "^2.1.15",
"@radix-ui/react-separator": "^1.1.15",
"@radix-ui/react-slot": "^1.3.3",
"@radix-ui/react-switch": "^1.3.7",
"@radix-ui/react-tabs": "^1.1.21",
@@ -0,0 +1,62 @@
-- CreateTable
CREATE TABLE "Proposal" (
"id" TEXT NOT NULL PRIMARY KEY,
"slug" TEXT NOT NULL,
"manageToken" TEXT NOT NULL,
"senderName" TEXT NOT NULL,
"recipientName" TEXT NOT NULL,
"title" TEXT NOT NULL,
"message" TEXT NOT NULL,
"theme" TEXT NOT NULL DEFAULT 'sunset',
"gradientFrom" TEXT,
"gradientVia" TEXT,
"gradientTo" TEXT,
"gifUrl" TEXT,
"evasiveNo" BOOLEAN NOT NULL DEFAULT false,
"status" TEXT NOT NULL DEFAULT 'draft',
"expiresAt" DATETIME,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateTable
CREATE TABLE "ActivityOption" (
"id" TEXT NOT NULL PRIMARY KEY,
"proposalId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"emoji" TEXT NOT NULL DEFAULT '🎉',
"order" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "ActivityOption_proposalId_fkey" FOREIGN KEY ("proposalId") REFERENCES "Proposal" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "TimeSlot" (
"id" TEXT NOT NULL PRIMARY KEY,
"activityOptionId" TEXT NOT NULL,
"label" TEXT NOT NULL,
"startsAt" TEXT,
CONSTRAINT "TimeSlot_activityOptionId_fkey" FOREIGN KEY ("activityOptionId") REFERENCES "ActivityOption" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Response" (
"id" TEXT NOT NULL PRIMARY KEY,
"proposalId" TEXT NOT NULL,
"selectedActivityId" TEXT,
"selectedTimeSlotId" TEXT,
"answer" TEXT NOT NULL,
"note" TEXT,
"respondedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Response_proposalId_fkey" FOREIGN KEY ("proposalId") REFERENCES "Proposal" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "Response_selectedActivityId_fkey" FOREIGN KEY ("selectedActivityId") REFERENCES "ActivityOption" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Response_selectedTimeSlotId_fkey" FOREIGN KEY ("selectedTimeSlotId") REFERENCES "TimeSlot" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Proposal_slug_key" ON "Proposal"("slug");
-- CreateIndex
CREATE UNIQUE INDEX "Proposal_manageToken_key" ON "Proposal"("manageToken");
-- CreateIndex
CREATE UNIQUE INDEX "Response_proposalId_key" ON "Response"("proposalId");
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "ActivityOption" ADD COLUMN "location" TEXT;
-36
View File
@@ -1,36 +0,0 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }
-121
View File
@@ -1,121 +0,0 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { cn } from "@/lib/utils"
import { X } from "lucide-react"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
-31
View File
@@ -1,31 +0,0 @@
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }