feat: support multi domain config
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { siteConfig } from "@/config/site";
|
||||
import HeroLanding, { LandingImages } from "@/components/sections/hero-landing";
|
||||
import { PricingSection } from "@/components/sections/pricing";
|
||||
|
||||
@@ -7,7 +8,6 @@ export default function IndexPage() {
|
||||
return (
|
||||
<>
|
||||
<HeroLanding />
|
||||
{/* <PreviewLanding /> */}
|
||||
<LandingImages />
|
||||
<PricingSection />
|
||||
</>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { siteConfig } from "@/config/site";
|
||||
|
||||
@@ -353,7 +353,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
<TableCell className="col-span-1 flex items-center gap-1">
|
||||
<Link
|
||||
className="overflow-hidden overflow-ellipsis whitespace-normal text-slate-600 hover:text-blue-400 hover:underline dark:text-slate-400"
|
||||
href={`/s/${short.url}`}
|
||||
href={`https://${short.prefix}/s/${short.url}`}
|
||||
target="_blank"
|
||||
prefetch={false}
|
||||
title={short.url}
|
||||
@@ -361,7 +361,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
{short.url}
|
||||
</Link>
|
||||
<CopyButton
|
||||
value={`${siteConfig.url}/s/${short.url}`}
|
||||
value={`${short.prefix}/s/${short.url}`}
|
||||
className={cn(
|
||||
"size-[25px]",
|
||||
"duration-250 transition-all group-hover:opacity-100",
|
||||
@@ -434,7 +434,9 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
onClick={() => {
|
||||
setSelectedUrlId(short.id!);
|
||||
setShowQrcode(!isShowQrcode);
|
||||
handleQrcode(`https://wr.do/s/${short.url}`);
|
||||
handleQrcode(
|
||||
`https://${short.prefix}/s/${short.url}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Icons.qrcode className="mx-0.5 size-4" />
|
||||
|
||||
@@ -25,13 +25,14 @@ export async function POST(req: Request) {
|
||||
|
||||
const { data } = await req.json();
|
||||
|
||||
const { target, url, visible, active, expiration } =
|
||||
const { target, url, prefix, visible, active, expiration } =
|
||||
createUrlSchema.parse(data);
|
||||
const res = await createUserShortUrl({
|
||||
userId: user.id,
|
||||
userName: user.name || "Anonymous",
|
||||
target,
|
||||
url,
|
||||
prefix,
|
||||
visible,
|
||||
active,
|
||||
expiration,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { env } from "@/env.mjs";
|
||||
import { getUserRecords } from "@/lib/dto/cloudflare-dns-record";
|
||||
import { deleteUserShortUrl } from "@/lib/dto/short-urls";
|
||||
import { checkUserStatus } from "@/lib/dto/user";
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function POST(req: Request) {
|
||||
});
|
||||
}
|
||||
|
||||
const { target, url, visible, active, id, expiration } =
|
||||
const { target, url, prefix, visible, active, id, expiration } =
|
||||
createUrlSchema.parse(data);
|
||||
const res = await updateUserShortUrl({
|
||||
id,
|
||||
@@ -32,6 +32,7 @@ export async function POST(req: Request) {
|
||||
userName: "",
|
||||
target,
|
||||
url,
|
||||
prefix,
|
||||
visible,
|
||||
active,
|
||||
expiration,
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
} from "@/lib/dto/short-urls";
|
||||
import { checkUserStatus } from "@/lib/dto/user";
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { createUrlSchema } from "@/lib/validations/url";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
|
||||
@@ -18,13 +18,14 @@ export async function POST(req: Request) {
|
||||
});
|
||||
}
|
||||
|
||||
const { target, url, visible, active, id, expiration } =
|
||||
const { target, url, prefix, visible, active, id, expiration } =
|
||||
createUrlSchema.parse(data);
|
||||
const res = await updateUserShortUrl({
|
||||
id,
|
||||
userId: user.id,
|
||||
userName: user.name || "Anonymous",
|
||||
target,
|
||||
prefix,
|
||||
url,
|
||||
visible,
|
||||
active,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Sparkles } from "lucide-react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { siteConfig } from "@/config/site";
|
||||
import { ShortUrlFormData } from "@/lib/dto/short-urls";
|
||||
import { EXPIRATION_ENUMS } from "@/lib/enums";
|
||||
import { generateUrlSuffix } from "@/lib/utils";
|
||||
@@ -63,6 +64,7 @@ export function UrlForm({
|
||||
target: initData?.target || "",
|
||||
url: initData?.url || "",
|
||||
active: initData?.active || 1,
|
||||
prefix: initData?.prefix || siteConfig.shortDomains[0],
|
||||
visible: initData?.visible || 0,
|
||||
expiration: initData?.expiration || "-1",
|
||||
},
|
||||
@@ -148,7 +150,7 @@ export function UrlForm({
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<div className="items-center justify-start gap-4 md:flex">
|
||||
<FormSectionColumns title="Target">
|
||||
<FormSectionColumns title="Target URL">
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<Label className="sr-only" htmlFor="target">
|
||||
Target
|
||||
@@ -172,19 +174,38 @@ export function UrlForm({
|
||||
)}
|
||||
</div>
|
||||
</FormSectionColumns>
|
||||
<FormSectionColumns title="Url Suffix">
|
||||
<FormSectionColumns title="Short Link">
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<Label className="sr-only" htmlFor="url">
|
||||
Url
|
||||
</Label>
|
||||
|
||||
<div className="relative flex items-center">
|
||||
<span className="pointer-events-none absolute left-3 whitespace-nowrap text-sm text-gray-400">
|
||||
https://wr.do/s/
|
||||
</span>
|
||||
<Select
|
||||
onValueChange={(value: string) => {
|
||||
setValue("prefix", value);
|
||||
}}
|
||||
name="prefix"
|
||||
defaultValue={initData?.prefix || siteConfig.shortDomains[0]}
|
||||
disabled={type === "edit"}
|
||||
>
|
||||
<SelectTrigger className="w-2/5 rounded-r-none shadow-inner">
|
||||
<SelectValue placeholder="Select a domain" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{siteConfig.shortDomains.map((v) => (
|
||||
<SelectItem key={v} value={v}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{/* <span className="pointer-events-none absolute left-20 whitespace-nowrap text-sm text-gray-400">
|
||||
/s/
|
||||
</span> */}
|
||||
<Input
|
||||
id="url"
|
||||
className="flex-1 rounded-r-none pl-[116px] shadow-inner"
|
||||
className="w-3/5 flex-1 rounded-none pl-[8px] shadow-inner"
|
||||
size={20}
|
||||
{...register("url")}
|
||||
disabled={type === "edit"}
|
||||
@@ -210,7 +231,7 @@ export function UrlForm({
|
||||
</p>
|
||||
) : (
|
||||
<p className="pb-0.5 text-[13px] text-muted-foreground">
|
||||
A random url suffix.
|
||||
A random url suffix. Final url like「wr.do/s/suffix」
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -241,7 +262,15 @@ export function UrlForm({
|
||||
Expiration time, default for never.
|
||||
</p>
|
||||
</FormSectionColumns>
|
||||
<FormSectionColumns title="Visible">
|
||||
{/* <div>
|
||||
<p className="text-sm text-gray-700 dark:text-white">
|
||||
Your Final URL:
|
||||
</p>
|
||||
<p className="text-sm text-gray-700 dark:text-white">
|
||||
{getValues("prefix")}/s/{getValues("url")}
|
||||
</p>
|
||||
</div> */}
|
||||
{/* <FormSectionColumns title="Visible">
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<Label className="sr-only" htmlFor="visible">
|
||||
Visible
|
||||
@@ -257,7 +286,7 @@ export function UrlForm({
|
||||
<p className="p-1 text-[13px] text-muted-foreground">
|
||||
Public or private short url.
|
||||
</p>
|
||||
</FormSectionColumns>
|
||||
</FormSectionColumns> */}
|
||||
{/* <FormSectionColumns title="Active">
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<Label className="sr-only" htmlFor="active">
|
||||
|
||||
@@ -5,6 +5,7 @@ const site_url = env.NEXT_PUBLIC_APP_URL;
|
||||
const free_recored_quota = env.NEXT_PUBLIC_FREE_RECORD_QUOTA;
|
||||
const free_url_quota = env.NEXT_PUBLIC_FREE_URL_QUOTA;
|
||||
const open_signup = env.NEXT_PUBLIC_OPEN_SIGNUP;
|
||||
const short_domains = env.NEXT_PUBLIC_SHORT_DOMAINS || "";
|
||||
|
||||
export const siteConfig: SiteConfig = {
|
||||
name: "WR.DO",
|
||||
@@ -22,6 +23,7 @@ export const siteConfig: SiteConfig = {
|
||||
url: Number(free_url_quota),
|
||||
},
|
||||
openSignup: open_signup === "1" ? true : false,
|
||||
shortDomains: short_domains.split(","),
|
||||
};
|
||||
|
||||
export const footerLinks: SidebarNavItem[] = [
|
||||
|
||||
@@ -26,6 +26,7 @@ export const env = createEnv({
|
||||
NEXT_PUBLIC_FREE_RECORD_QUOTA: z.string().min(1).default("3"),
|
||||
NEXT_PUBLIC_FREE_URL_QUOTA: z.string().min(1).default("100"),
|
||||
NEXT_PUBLIC_OPEN_SIGNUP: z.string().min(1).default("1"),
|
||||
NEXT_PUBLIC_SHORT_DOMAINS: z.string().min(1).default(""),
|
||||
},
|
||||
runtimeEnv: {
|
||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
||||
@@ -40,6 +41,7 @@ export const env = createEnv({
|
||||
NEXT_PUBLIC_FREE_RECORD_QUOTA: process.env.NEXT_PUBLIC_FREE_RECORD_QUOTA,
|
||||
NEXT_PUBLIC_FREE_URL_QUOTA: process.env.NEXT_PUBLIC_FREE_URL_QUOTA,
|
||||
NEXT_PUBLIC_OPEN_SIGNUP: process.env.NEXT_PUBLIC_OPEN_SIGNUP,
|
||||
NEXT_PUBLIC_SHORT_DOMAINS: process.env.NEXT_PUBLIC_SHORT_DOMAINS,
|
||||
CLOUDFLARE_ZONE_ID: process.env.CLOUDFLARE_ZONE_ID,
|
||||
CLOUDFLARE_API_KEY: process.env.CLOUDFLARE_API_KEY,
|
||||
CLOUDFLARE_EMAIL: process.env.CLOUDFLARE_EMAIL,
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface ShortUrlFormData {
|
||||
userName: string;
|
||||
target: string;
|
||||
url: string;
|
||||
prefix: string;
|
||||
visible: number;
|
||||
active: number;
|
||||
expiration: string;
|
||||
@@ -103,6 +104,7 @@ export async function createUserShortUrl(data: ShortUrlFormData) {
|
||||
userName: data.userName || "Anonymous",
|
||||
target: data.target,
|
||||
url: data.url,
|
||||
prefix: data.prefix,
|
||||
visible: data.visible,
|
||||
active: data.active,
|
||||
expiration: data.expiration,
|
||||
@@ -127,6 +129,7 @@ export async function updateUserShortUrl(data: ShortUrlFormData) {
|
||||
target: data.target,
|
||||
url: data.url,
|
||||
visible: data.visible,
|
||||
prefix: data.prefix,
|
||||
// active: data.active,
|
||||
expiration: data.expiration,
|
||||
updatedAt: new Date().toISOString(),
|
||||
@@ -206,6 +209,7 @@ export async function getUrlBySuffix(suffix: string) {
|
||||
id: true,
|
||||
target: true,
|
||||
active: true,
|
||||
prefix: true,
|
||||
expiration: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
|
||||
@@ -23,4 +23,5 @@ export const createUrlSchema = z.object({
|
||||
expiration: z.string().default("-1"),
|
||||
visible: z.number().default(1),
|
||||
active: z.number().default(1),
|
||||
prefix: z.string().default("wr.do"),
|
||||
});
|
||||
|
||||
@@ -126,6 +126,7 @@ CREATE TABLE "user_urls"
|
||||
"userName" TEXT NOT NULL,
|
||||
"target" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"prefix" TEXT NOT NULL,
|
||||
"visible" INTEGER NOT NULL DEFAULT 0,
|
||||
"active" INTEGER NOT NULL DEFAULT 1,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
@@ -110,6 +110,7 @@ model UserUrl {
|
||||
userName String
|
||||
target String
|
||||
url String @unique
|
||||
prefix String
|
||||
visible Int @default(0)
|
||||
active Int @default(1)
|
||||
expiration String @default("-1")
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -19,6 +19,7 @@ export type SiteConfig = {
|
||||
url: number;
|
||||
};
|
||||
openSignup: boolean;
|
||||
shortDomains: string[];
|
||||
};
|
||||
|
||||
export type NavItem = {
|
||||
|
||||
Reference in New Issue
Block a user