Compare commits
39 Commits
577b863216
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c08c4024c | |||
| d36b14852f | |||
| 0682972459 | |||
| 9a4b32b374 | |||
| 9ad57a408f | |||
| 78c00d7af6 | |||
| a5a2838448 | |||
| 16efb43bec | |||
| 4824621d9c | |||
| 72108b1d3f | |||
| 2981b2be2b | |||
| 2be5276210 | |||
| 4ac430c1fe | |||
| 6a63e2801d | |||
| 2a52e166a4 | |||
| beee4e9eeb | |||
| d4dffd7520 | |||
| 5e971e96db | |||
| 70ce50625c | |||
| 9a8f521366 | |||
| 2d78f719ff | |||
| 7e5c12c1d1 | |||
| 44fe466634 | |||
| 8317dc2246 | |||
| 05f442fea1 | |||
| dcf4f4d3a6 | |||
| aef6f72396 | |||
| d3982abd20 | |||
| 8e2b3176e6 | |||
| 9241b2c1b7 | |||
| 842c0fa555 | |||
| 9b3a71b1b5 | |||
| 42d7e9926b | |||
| 920eefbd65 | |||
| 2238d794b6 | |||
| 64267eb15d | |||
| 17a1598afa | |||
| a6284f1927 | |||
| 55a9835845 |
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
git.fossy.my.id/${{ secrets.DOCKER_USERNAME }}/tunnl_please_frontend:latest
|
git.fossy.my.id/${{ secrets.DOCKER_USERNAME }}/tunnel-please-frontend:latest
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
|
|
||||||
@@ -62,6 +62,6 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
git.fossy.my.id/${{ secrets.DOCKER_USERNAME }}/tunnl_please_frontend:staging
|
git.fossy.my.id/${{ secrets.DOCKER_USERNAME }}/tunnel-please-frontend:staging
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
if: github.ref == 'refs/heads/staging'
|
if: github.ref == 'refs/heads/staging'
|
||||||
31
Dockerfile
31
Dockerfile
@@ -1,28 +1,29 @@
|
|||||||
FROM node:24-alpine AS builder
|
FROM node:24-slim AS base
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
FROM base AS deps
|
||||||
|
ENV NODE_ENV=development
|
||||||
COPY package.json package-lock.json* ./
|
COPY package.json package-lock.json* ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM node:24-alpine AS runner
|
FROM gcr.io/distroless/nodejs24-debian13 AS runner
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
|
||||||
RUN npm ci --omit=dev
|
|
||||||
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
|
|
||||||
#COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
ENV PORT=3000
|
||||||
|
ENV HOSTNAME=0.0.0.0
|
||||||
|
|
||||||
|
COPY --from=builder /app/.next/standalone ./
|
||||||
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
|
# COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["npm", "start"]
|
CMD ["server.js"]
|
||||||
|
|||||||
60
app/api/session/[...params]/route.ts
Normal file
60
app/api/session/[...params]/route.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
import { headers } from "next/headers"
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
export async function PATCH(req: NextRequest, context: { params: Promise<{ params: string[] }> }) {
|
||||||
|
const { params } = await context.params;
|
||||||
|
const requestHeaders = await headers();
|
||||||
|
const result = await auth.api.getToken({ headers: requestHeaders }).catch(() => null);
|
||||||
|
if (!result || typeof result !== "object" || !("token" in result)) {
|
||||||
|
return new Response("Unauthorized", { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { token } = result as { token: string };
|
||||||
|
if (!token) {
|
||||||
|
return new Response("Unauthorized", { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch(`${process.env.API_URL}/api/session/${params.join("/")}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
cache: "no-store",
|
||||||
|
body: await req.text(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(req: NextRequest, context: { params: Promise<{ params: string[] }> }) {
|
||||||
|
const { params } = await context.params;
|
||||||
|
if (!params || params.length < 3) {
|
||||||
|
return new Response("Bad Request", { status: 400 });
|
||||||
|
}
|
||||||
|
const node = params[0]
|
||||||
|
const tunnelType = params[1]
|
||||||
|
const slug = params[2]
|
||||||
|
|
||||||
|
const requestHeaders = await headers();
|
||||||
|
const result = await auth.api.getToken({ headers: requestHeaders }).catch(() => null);
|
||||||
|
if (!result || typeof result !== "object" || !("token" in result)) {
|
||||||
|
return new Response("Unauthorized", { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { token } = result as { token: string };
|
||||||
|
if (!token) {
|
||||||
|
return new Response("Unauthorized", { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch(`${process.env.API_URL}/api/session/${node}/${tunnelType}/${slug}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
cache: "no-store",
|
||||||
|
body: await req.text(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
556
app/dashboard/dashboard-client.tsx
Normal file
556
app/dashboard/dashboard-client.tsx
Normal file
@@ -0,0 +1,556 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect, useState, type FormEvent } from "react"
|
||||||
|
import Link from "next/link"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import TunnelConfig, { type TunnelConfig as TunnelConfigType, type Server } from "@/components/tunnel-config"
|
||||||
|
import { authClient } from "@/lib/auth-client"
|
||||||
|
|
||||||
|
const defaultConfig: TunnelConfigType = {
|
||||||
|
type: "http",
|
||||||
|
serverPort: 443,
|
||||||
|
localPort: 8000,
|
||||||
|
}
|
||||||
|
|
||||||
|
const STOP_CONFIRMATION_TEXT = "YEAH SURE BUDDY"
|
||||||
|
|
||||||
|
const formatStartedAgo = (timestamp?: ApiTimestamp): string | undefined => {
|
||||||
|
if (!timestamp) return undefined
|
||||||
|
const startedMs = timestamp.seconds * 1000 + Math.floor(timestamp.nanos / 1_000_000)
|
||||||
|
const diffSeconds = Math.max(0, Math.floor((Date.now() - startedMs) / 1000))
|
||||||
|
|
||||||
|
if (diffSeconds < 60) return `${diffSeconds}s ago`
|
||||||
|
const diffMinutes = Math.floor(diffSeconds / 60)
|
||||||
|
if (diffMinutes < 60) return `${diffMinutes}m ago`
|
||||||
|
const diffHours = Math.floor(diffMinutes / 60)
|
||||||
|
if (diffHours < 24) return `${diffHours}h ago`
|
||||||
|
const diffDays = Math.floor(diffHours / 24)
|
||||||
|
return `${diffDays}d ago`
|
||||||
|
}
|
||||||
|
|
||||||
|
const toActiveConnection = (session: ApiSession): ActiveConnection => {
|
||||||
|
const startedAgo = formatStartedAgo(session.started_at)
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: session.slug || `${session.node}-${session.started_at?.seconds ?? Date.now()}`,
|
||||||
|
name: session.slug || session.node || "Unknown tunnel",
|
||||||
|
slug: session.slug,
|
||||||
|
status: session.active ? "connected" : "error",
|
||||||
|
protocol: (session.forwarding_type || "http").toLowerCase(),
|
||||||
|
serverLabel: session.node || "Unknown node",
|
||||||
|
node: session.node,
|
||||||
|
remote: session.slug ? `${session.slug}.${session.node}` : session.node || "—",
|
||||||
|
startedAgo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApiTimestamp = {
|
||||||
|
seconds: number
|
||||||
|
nanos: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApiSession = {
|
||||||
|
node: string
|
||||||
|
forwarding_type: "HTTP" | "HTTPS" | "TCP" | string
|
||||||
|
slug: string
|
||||||
|
user_id: string
|
||||||
|
active: boolean
|
||||||
|
started_at?: ApiTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApiSessionList = ApiSession[]
|
||||||
|
|
||||||
|
type SessionResponse = Awaited<ReturnType<typeof authClient.getSession>>
|
||||||
|
|
||||||
|
interface DashboardClientProps {
|
||||||
|
initialActiveConnections: ApiSessionList
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActiveConnectionStatus = "connected" | "pending" | "error"
|
||||||
|
|
||||||
|
type ActiveConnection = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
slug?: string
|
||||||
|
status: ActiveConnectionStatus
|
||||||
|
protocol: string
|
||||||
|
serverLabel: string
|
||||||
|
node?: string
|
||||||
|
remote: string
|
||||||
|
localPort?: number
|
||||||
|
serverPort?: number
|
||||||
|
startedAgo?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DashboardClient({ initialActiveConnections }: DashboardClientProps) {
|
||||||
|
const router = useRouter()
|
||||||
|
const [selectedServer, setSelectedServer] = useState<Server | null>(null)
|
||||||
|
const [tunnelConfig, setTunnelConfig] = useState<TunnelConfigType>(defaultConfig)
|
||||||
|
const [statusMessage, setStatusMessage] = useState<string | null>(null)
|
||||||
|
const [activeConnections, setActiveConnections] = useState<ActiveConnection[]>(
|
||||||
|
initialActiveConnections.map(toActiveConnection),
|
||||||
|
)
|
||||||
|
const { data: cachedSession } = authClient.useSession()
|
||||||
|
const [session, setSession] = useState<SessionResponse["data"] | null>(cachedSession ?? null)
|
||||||
|
const [openActionId, setOpenActionId] = useState<string | null>(null)
|
||||||
|
const [stopModal, setStopModal] = useState<{
|
||||||
|
connectionId: string
|
||||||
|
node?: string
|
||||||
|
slug?: string
|
||||||
|
protocol?: string
|
||||||
|
name: string
|
||||||
|
} | null>(null)
|
||||||
|
const [stopModalInput, setStopModalInput] = useState("")
|
||||||
|
const [stopSaving, setStopSaving] = useState(false)
|
||||||
|
const [stopError, setStopError] = useState<string | null>(null)
|
||||||
|
const [slugModal, setSlugModal] = useState<{
|
||||||
|
connectionId: string
|
||||||
|
currentSlug: string
|
||||||
|
newSlug: string
|
||||||
|
node: string
|
||||||
|
} | null>(null)
|
||||||
|
const [slugError, setSlugError] = useState<string | null>(null)
|
||||||
|
const [slugSaving, setSlugSaving] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setActiveConnections(initialActiveConnections.map(toActiveConnection))
|
||||||
|
}, [initialActiveConnections])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!session && cachedSession) {
|
||||||
|
setSession(cachedSession)
|
||||||
|
}
|
||||||
|
}, [cachedSession, session])
|
||||||
|
|
||||||
|
const openStopModal = (connection: ActiveConnection) => {
|
||||||
|
setStopModal({
|
||||||
|
connectionId: connection.id,
|
||||||
|
node: connection.node || connection.serverLabel,
|
||||||
|
slug: connection.slug || connection.name,
|
||||||
|
protocol: connection.protocol,
|
||||||
|
name: connection.name,
|
||||||
|
})
|
||||||
|
setStopModalInput("")
|
||||||
|
setStopError(null)
|
||||||
|
setOpenActionId(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeStopModal = () => {
|
||||||
|
setStopModal(null)
|
||||||
|
setStopModalInput("")
|
||||||
|
setStopSaving(false)
|
||||||
|
setStopError(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopConnection = async () => {
|
||||||
|
if (!stopModal) return
|
||||||
|
|
||||||
|
if (stopModalInput.trim() !== STOP_CONFIRMATION_TEXT) {
|
||||||
|
setStopError("Please type the confirmation phrase exactly.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = stopModal.node
|
||||||
|
const tunnelType = stopModal.protocol?.toLowerCase()
|
||||||
|
const slug = stopModal.slug
|
||||||
|
|
||||||
|
if (!node || !tunnelType || !slug) {
|
||||||
|
setStopError("Missing connection details; cannot stop.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setStopSaving(true)
|
||||||
|
setStopError(null)
|
||||||
|
setStatusMessage(null)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const endpoint = `/api/session/${encodeURIComponent(node)}/${encodeURIComponent(tunnelType)}/${encodeURIComponent(slug)}`
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: "DELETE",
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const message = await response.text()
|
||||||
|
setStopError(message || "Failed to stop connection.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveConnections((prev) => prev.filter((conn) => conn.id !== stopModal.connectionId))
|
||||||
|
setStatusMessage("Connection stopped")
|
||||||
|
closeStopModal()
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to stop connection", error)
|
||||||
|
setStopError("Failed to stop connection.")
|
||||||
|
} finally {
|
||||||
|
setStopSaving(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openChangeSlugModal = (connection: ActiveConnection) => {
|
||||||
|
setSlugModal({
|
||||||
|
connectionId: connection.id,
|
||||||
|
currentSlug: connection.name,
|
||||||
|
newSlug: connection.name,
|
||||||
|
node: connection.node || connection.serverLabel,
|
||||||
|
})
|
||||||
|
setSlugError(null)
|
||||||
|
setOpenActionId(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSlugModal = () => setSlugModal(null)
|
||||||
|
|
||||||
|
const validateSlug = (value: string): string | null => {
|
||||||
|
const trimmed = value.trim().toLowerCase()
|
||||||
|
if (trimmed.length < 3 || trimmed.length > 20) return "Slug must be 3-20 characters."
|
||||||
|
if (!/^[a-z0-9-]+$/.test(trimmed)) return "Only lowercase letters, numbers, and hyphens are allowed."
|
||||||
|
if (trimmed.startsWith("-") || trimmed.endsWith("-")) return "No leading or trailing hyphens."
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitSlugChange = async (event: FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (!slugModal) return
|
||||||
|
|
||||||
|
const trimmedSlug = slugModal.newSlug.trim().toLowerCase()
|
||||||
|
const validationError = validateSlug(trimmedSlug)
|
||||||
|
setSlugError(validationError)
|
||||||
|
if (validationError) return
|
||||||
|
|
||||||
|
setSlugSaving(true)
|
||||||
|
setStatusMessage(null)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/session/${slugModal.node}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ old: slugModal.currentSlug, new: trimmedSlug }),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const message = await response.text()
|
||||||
|
setSlugError(message || "Failed to update slug.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveConnections((prev) =>
|
||||||
|
prev.map((conn) =>
|
||||||
|
conn.id === slugModal.connectionId
|
||||||
|
? {
|
||||||
|
...conn,
|
||||||
|
name: trimmedSlug,
|
||||||
|
remote: conn.node ? `${trimmedSlug}.${conn.node}` : trimmedSlug,
|
||||||
|
}
|
||||||
|
: conn,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
setStatusMessage("Slug updated")
|
||||||
|
setSlugModal(null)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to update slug", error)
|
||||||
|
setSlugError("Failed to update slug.")
|
||||||
|
} finally {
|
||||||
|
setSlugSaving(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (slugModal || stopModal) {
|
||||||
|
const previousOverflow = document.body.style.overflow
|
||||||
|
document.body.style.overflow = "hidden"
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = previousOverflow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [slugModal, stopModal])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const closeOnEscape = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
setOpenActionId(null)
|
||||||
|
setSlugModal(null)
|
||||||
|
setStopModal(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("keydown", closeOnEscape)
|
||||||
|
return () => window.removeEventListener("keydown", closeOnEscape)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const stopModalContent = !stopModal
|
||||||
|
? null
|
||||||
|
: (
|
||||||
|
<div className="fixed inset-0 z-30 flex items-center justify-center bg-black/60 px-4">
|
||||||
|
<div className="w-full max-w-md rounded-lg border border-gray-800 bg-gray-900 p-6 shadow-xl">
|
||||||
|
<div className="flex items-start justify-between gap-4 mb-6">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-red-500/10 ring-1 ring-red-500/20">
|
||||||
|
<svg className="h-5 w-5 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-white">Stop connection</h3>
|
||||||
|
<p className="mt-1 text-sm text-gray-400 leading-relaxed">
|
||||||
|
Type <span className="font-mono text-xs text-red-300 bg-red-950/50 px-1.5 py-0.5 rounded">{STOP_CONFIRMATION_TEXT}</span> to stop <span className="font-medium text-gray-300">{stopModal.slug || stopModal.name}</span> on {stopModal.node || "this node"}.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={closeStopModal}
|
||||||
|
className="flex h-8 w-8 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-gray-800 hover:text-gray-200"
|
||||||
|
aria-label="Close modal"
|
||||||
|
>
|
||||||
|
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 space-y-4">
|
||||||
|
<label className="block text-sm text-gray-300">
|
||||||
|
Confirmation phrase
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={stopModalInput}
|
||||||
|
onChange={(e) => {
|
||||||
|
setStopModalInput(e.target.value)
|
||||||
|
if (stopError) setStopError(null)
|
||||||
|
}}
|
||||||
|
placeholder={STOP_CONFIRMATION_TEXT}
|
||||||
|
className="mt-2 w-full rounded-md border border-gray-700 bg-gray-800 px-3 py-2 text-white focus:border-emerald-500 focus:outline-none"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{stopError && <p className="text-sm text-red-400">{stopError}</p>}
|
||||||
|
<div className="flex items-center justify-end gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={closeStopModal}
|
||||||
|
className="rounded-md px-4 py-2 text-sm font-medium bg-gray-700 text-gray-200 border border-gray-700 hover:bg-gray-600 hover:border-gray-500"
|
||||||
|
disabled={stopSaving}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={stopConnection}
|
||||||
|
disabled={stopModalInput.trim() !== STOP_CONFIRMATION_TEXT || stopSaving}
|
||||||
|
className={`rounded-md px-4 py-2 text-sm font-medium text-white${stopModalInput.trim() === STOP_CONFIRMATION_TEXT && !stopSaving ? 'bg-red-600 hover:bg-red-700 border border-gray-500' : 'bg-red-900 hover:text-white-300 cursor-not-allowed border border-gray-700'}`}>
|
||||||
|
{stopSaving ? "Stopping..." : "Confirm stop"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const slugModalContent = !slugModal
|
||||||
|
? null
|
||||||
|
: (
|
||||||
|
<div className="fixed inset-0 z-20 flex items-center justify-center bg-black/60 px-4">
|
||||||
|
<div className="w-full max-w-md rounded-lg border border-gray-800 bg-gray-900 p-6 shadow-xl">
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-white">Change slug</h3>
|
||||||
|
<p className="text-sm text-gray-400">Update the identifier for this tunnel.</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={closeSlugModal}
|
||||||
|
className="text-gray-400 hover:text-gray-200"
|
||||||
|
aria-label="Close modal"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={submitSlugChange} className="mt-4 space-y-4">
|
||||||
|
<label className="block text-sm text-gray-300">
|
||||||
|
New slug
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={slugModal.newSlug}
|
||||||
|
onChange={(e) => {
|
||||||
|
const nextValue = e.target.value.toLowerCase()
|
||||||
|
setSlugModal((prev) => (prev ? { ...prev, newSlug: nextValue } : prev))
|
||||||
|
setSlugError(validateSlug(nextValue))
|
||||||
|
}}
|
||||||
|
className="mt-2 w-full rounded-md border border-gray-700 bg-gray-800 px-3 py-2 text-white focus:border-emerald-500 focus:outline-none"
|
||||||
|
placeholder={slugModal.currentSlug}
|
||||||
|
/>
|
||||||
|
{slugError && <p className="mt-2 text-sm text-red-400">{slugError}</p>}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-end gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={closeSlugModal}
|
||||||
|
className="rounded-md border border-gray-700 px-4 py-2 text-sm text-gray-200 hover:border-gray-500"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={Boolean(slugError) || slugSaving}
|
||||||
|
className="rounded-md bg-emerald-600 px-4 py-2 text-sm font-medium text-white hover:bg-emerald-500 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
>
|
||||||
|
{slugSaving ? "Saving..." : "Save slug"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="flex-1">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 py-8 space-y-6">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<h1 className="text-2xl font-semibold">Active Forwarding</h1>
|
||||||
|
<p className="text-sm text-gray-400">Live tunnels for this session.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{statusMessage && (
|
||||||
|
<div className="rounded-lg border border-emerald-700 bg-emerald-900/40 px-4 py-3 text-sm text-emerald-200">
|
||||||
|
{statusMessage}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-gray-800 bg-gray-900 p-5">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold">Active Connections</h2>
|
||||||
|
<p className="text-sm text-gray-400">Monitor and manage your running tunnels</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => router.refresh()}
|
||||||
|
className="text-sm text-emerald-400 hover:text-emerald-300"
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{activeConnections.length === 0 ? (
|
||||||
|
<div className="rounded-lg border border-dashed border-gray-700 bg-gray-800/60 p-6 text-center text-gray-400">
|
||||||
|
No active connections yet. Configure a tunnel to see it here.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{activeConnections.map((connection) => {
|
||||||
|
const metaParts: string[] = []
|
||||||
|
if (connection.localPort && connection.serverPort) {
|
||||||
|
metaParts.push(`Local ${connection.localPort} → Server ${connection.serverPort}`)
|
||||||
|
}
|
||||||
|
if (connection.startedAgo) {
|
||||||
|
metaParts.push(connection.startedAgo)
|
||||||
|
}
|
||||||
|
const metaText = metaParts.length > 0 ? metaParts.join(" · ") : "No session metadata yet"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={connection.id}
|
||||||
|
className="rounded-lg border border-gray-800 bg-gray-800/60 p-4 flex flex-col gap-3 md:flex-row md:items-center md:justify-between"
|
||||||
|
>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-white font-medium">{connection.name}</span>
|
||||||
|
<span
|
||||||
|
className={`rounded-full px-2 py-0.5 text-xs font-semibold ${connection.status === "connected"
|
||||||
|
? "bg-emerald-900/60 text-emerald-300 border border-emerald-700"
|
||||||
|
: connection.status === "pending"
|
||||||
|
? "bg-yellow-900/60 text-yellow-300 border border-yellow-700"
|
||||||
|
: "bg-red-900/60 text-red-300 border border-red-700"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{connection.status === "connected"
|
||||||
|
? "Connected"
|
||||||
|
: connection.status === "pending"
|
||||||
|
? "Reconnecting"
|
||||||
|
: "Error"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-300">
|
||||||
|
{(connection.protocol || "http").toUpperCase()} · {connection.serverLabel}
|
||||||
|
</p>
|
||||||
|
{(() => {
|
||||||
|
const isTcp = connection.protocol === "tcp"
|
||||||
|
const isHttp = connection.protocol === "http" || connection.protocol === "https"
|
||||||
|
const httpRemote = connection.remote ? `https://${connection.remote}` : "—"
|
||||||
|
const tcpRemote =
|
||||||
|
connection.node && connection.name
|
||||||
|
? `tcp://${connection.node}:${connection.name}`
|
||||||
|
: connection.remote || "—"
|
||||||
|
|
||||||
|
const displayRemote = isTcp ? tcpRemote : isHttp ? httpRemote : connection.remote || "—"
|
||||||
|
|
||||||
|
return <p className="text-xs text-gray-400">{displayRemote}</p>
|
||||||
|
})()}
|
||||||
|
<p className="text-xs text-gray-500">{metaText}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap items-center gap-3 md:justify-end relative">
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
setOpenActionId((current) => (current === connection.id ? null : connection.id))
|
||||||
|
}
|
||||||
|
className="rounded-lg border border-gray-700 px-3 py-2 text-sm text-gray-200 hover:border-gray-500 transition"
|
||||||
|
>
|
||||||
|
Actions
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{openActionId === connection.id && (
|
||||||
|
<div className="absolute right-0 top-12 z-10 w-44 rounded-md border border-gray-700 bg-gray-800 shadow-lg">
|
||||||
|
<button
|
||||||
|
className="w-full px-3 py-2 text-left text-sm text-gray-200 hover:bg-gray-700"
|
||||||
|
onClick={() => openStopModal(connection)}
|
||||||
|
>
|
||||||
|
Stop connection
|
||||||
|
</button>
|
||||||
|
{connection.protocol !== "tcp" && (
|
||||||
|
<button
|
||||||
|
className="w-full px-3 py-2 text-left text-sm text-gray-200 hover:bg-gray-700"
|
||||||
|
onClick={() => openChangeSlugModal(connection)}
|
||||||
|
>
|
||||||
|
Change slug
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-gray-800 bg-gray-900 p-5">
|
||||||
|
<div className="flex flex-col gap-2 mb-4 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold">Custom Tunnel Configuration</h2>
|
||||||
|
<p className="text-sm text-gray-400">Pick a location, test latency, and shape your tunnel exactly how you need.</p>
|
||||||
|
</div>
|
||||||
|
<Link href="/tunnel-not-found" className="text-sm text-emerald-400 hover:text-emerald-300">
|
||||||
|
View docs
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TunnelConfig
|
||||||
|
config={tunnelConfig}
|
||||||
|
onConfigChange={setTunnelConfig}
|
||||||
|
selectedServer={selectedServer}
|
||||||
|
onServerSelect={setSelectedServer}
|
||||||
|
isAuthenticated={Boolean(session)}
|
||||||
|
userId={session?.user?.sshIdentifier}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{stopModalContent}
|
||||||
|
{slugModalContent}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
40
app/dashboard/page.tsx
Normal file
40
app/dashboard/page.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import SiteHeader from "@/components/site-header"
|
||||||
|
import SiteFooter from "@/components/site-footer"
|
||||||
|
import { auth } from "@/lib/auth"
|
||||||
|
import { headers } from "next/headers"
|
||||||
|
import DashboardClient from "./dashboard-client"
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export default async function DashboardPage() {
|
||||||
|
const requestHeaders = await headers()
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: requestHeaders,
|
||||||
|
}).catch(() => {
|
||||||
|
redirect('/')
|
||||||
|
})
|
||||||
|
|
||||||
|
const { token } = await auth.api.getToken({
|
||||||
|
headers: requestHeaders,
|
||||||
|
}).catch(() => {
|
||||||
|
redirect('/')
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await fetch(`${process.env.API_URL}/api/sessions`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
cache: "no-store",
|
||||||
|
})
|
||||||
|
const initialActiveConnections = await data.json()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col bg-gray-950 text-white">
|
||||||
|
<SiteHeader session={session} />
|
||||||
|
<DashboardClient
|
||||||
|
initialActiveConnections={initialActiveConnections}
|
||||||
|
/>
|
||||||
|
<SiteFooter />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
1153
app/page.tsx
1153
app/page.tsx
File diff suppressed because it is too large
Load Diff
80
app/settings/page.tsx
Normal file
80
app/settings/page.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { authClient } from "@/lib/auth-client"
|
||||||
|
import SiteHeader from "@/components/site-header"
|
||||||
|
import SiteFooter from "@/components/site-footer"
|
||||||
|
|
||||||
|
export default function SettingsPage() {
|
||||||
|
const [requireAuth, setRequireAuth] = useState(true)
|
||||||
|
const [message, setMessage] = useState<string | null>(null)
|
||||||
|
const { data: session, isPending } = authClient.useSession();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPending && !session) {
|
||||||
|
router.push('/login');
|
||||||
|
}
|
||||||
|
}, [session, isPending, router]);
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleToggle = (value: boolean) => {
|
||||||
|
setRequireAuth(value)
|
||||||
|
setMessage(value ? "Authentication required for tunnel requests" : "Authentication not required for tunnel requests")
|
||||||
|
setTimeout(() => setMessage(null), 2500)
|
||||||
|
}
|
||||||
|
|
||||||
|
return session ? (
|
||||||
|
<div className="flex min-h-screen flex-col bg-gray-950 text-white">
|
||||||
|
<SiteHeader session={session} />
|
||||||
|
|
||||||
|
<main className="flex-1">
|
||||||
|
<div className="max-w-5xl mx-auto px-4 py-8 space-y-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h1 className="text-2xl font-semibold">Settings</h1>
|
||||||
|
<p className="text-sm text-gray-400">Control how tunnels can be requested.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{message && (
|
||||||
|
<div className="rounded-lg border border-emerald-700 bg-emerald-900/40 px-4 py-3 text-sm text-emerald-200">
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-gray-800 bg-gray-900 p-5 space-y-4">
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold">Tunnel Request Authentication</h2>
|
||||||
|
<p className="text-sm text-gray-400">Require users to be authenticated before they can request a tunnel.</p>
|
||||||
|
</div>
|
||||||
|
<label className="inline-flex items-center gap-2 cursor-pointer select-none">
|
||||||
|
<span className="text-sm text-gray-300">{requireAuth ? "Required" : "Not required"}</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={requireAuth}
|
||||||
|
onChange={(e) => handleToggle(e.target.checked)}
|
||||||
|
className="sr-only"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={`relative inline-flex h-6 w-11 items-center rounded-full border ${requireAuth ? "bg-emerald-600 border-emerald-500" : "bg-gray-700 border-gray-600"}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`inline-block h-5 w-5 rounded-full bg-white shadow transition-transform ${requireAuth ? "translate-x-5" : "translate-x-1"}`}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
This toggle is local-only for now. Wire it to your backend when ready to enforce tunnel request policies.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<SiteFooter />
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
35
components/site-footer.tsx
Normal file
35
components/site-footer.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import TunnlLogo from "./tunnl-logo"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
|
export default function SiteFooter() {
|
||||||
|
return (
|
||||||
|
<footer className="border-t border-gray-800 py-6 px-4">
|
||||||
|
<div className="max-w-3xl mx-auto text-center">
|
||||||
|
<div className="flex items-center justify-center gap-2 mb-4">
|
||||||
|
<TunnlLogo />
|
||||||
|
<span className="text-xl font-bold">
|
||||||
|
<span className="text-emerald-400">tunnl</span>.live
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-x-6 gap-y-2 mb-4 text-sm">
|
||||||
|
<Link href="https://status.fossy.my.id/status/services" className="text-gray-400 hover:text-emerald-400 transition-colors">
|
||||||
|
Status Page
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-500">
|
||||||
|
© {new Date().getFullYear()} tunnl.live. Made with ❤️ by{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/fossyy"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline hover:text-gray-700"
|
||||||
|
>
|
||||||
|
Bagas
|
||||||
|
</a>. All rights reserved.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
96
components/site-header.tsx
Normal file
96
components/site-header.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import Link from "next/link"
|
||||||
|
import TunnlLogo from "./tunnl-logo"
|
||||||
|
import UserMenu from "./user-menu"
|
||||||
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
import { redirect, RedirectType } from 'next/navigation'
|
||||||
|
|
||||||
|
type UseSessionReturn = ReturnType<typeof authClient.useSession>;
|
||||||
|
type SessionType = UseSessionReturn extends { data: infer D } ? D : never;
|
||||||
|
type SiteHeaderProps = {
|
||||||
|
session?: SessionType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SiteHeader({ session }: SiteHeaderProps) {
|
||||||
|
const logout = async () => {
|
||||||
|
await authClient.signOut({
|
||||||
|
fetchOptions: {
|
||||||
|
onSuccess: () => {
|
||||||
|
redirect('/login', RedirectType.replace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<header className="border-b border-gray-800 bg-gray-900/50 backdrop-blur-sm">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 py-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<TunnlLogo />
|
||||||
|
<span className="text-xl font-bold">
|
||||||
|
<span className="text-emerald-400">tunnl</span>.live
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
{session ? (
|
||||||
|
<UserMenu
|
||||||
|
user={{
|
||||||
|
name: session.user?.name ?? "User",
|
||||||
|
email: session.user.email ?? "",
|
||||||
|
image: session.user.image ?? undefined,
|
||||||
|
}}
|
||||||
|
onSignOut={logout}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="flex items-center gap-2 bg-emerald-600 hover:bg-emerald-700 text-white px-4 py-2 rounded-lg font-medium transition-colors"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
|
||||||
|
<polyline points="10 17 15 12 10 7" />
|
||||||
|
<line x1="15" x2="3" y1="12" y2="12" />
|
||||||
|
</svg>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className="hidden md:flex items-center gap-2 text-sm text-gray-400">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className="text-emerald-400"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<path d="M12 16v-4" />
|
||||||
|
<path d="M12 8h.01" />
|
||||||
|
</svg>
|
||||||
|
<span>Sign in to save configurations & view history</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import { ComposableMap, Geographies, Geography, Marker } from "react-simple-maps
|
|||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
|
||||||
export interface TunnelConfig {
|
export interface TunnelConfig {
|
||||||
type: "http" | "tcp"
|
type: "http" | "tcp" | "custom-domain"
|
||||||
serverPort: number
|
serverPort: number
|
||||||
localPort: number
|
localPort: number
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,7 @@ interface TunnelConfigProps {
|
|||||||
selectedServer: Server | null
|
selectedServer: Server | null
|
||||||
onServerSelect: (server: Server) => void
|
onServerSelect: (server: Server) => void
|
||||||
isAuthenticated?: boolean
|
isAuthenticated?: boolean
|
||||||
|
userId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchServers = async (): Promise<Server[]> => {
|
const fetchServers = async (): Promise<Server[]> => {
|
||||||
@@ -55,7 +56,35 @@ const fetchServers = async (): Promise<Server[]> => {
|
|||||||
pingStatus: "idle",
|
pingStatus: "idle",
|
||||||
capabilities: {
|
capabilities: {
|
||||||
http: true,
|
http: true,
|
||||||
tcp: false,
|
tcp: true,
|
||||||
|
},
|
||||||
|
portRestrictions: {
|
||||||
|
allowedRanges: [
|
||||||
|
{ min: 20000, max: 21000 },
|
||||||
|
],
|
||||||
|
blockedPorts: [8080, 8443, 9000],
|
||||||
|
supportsAutoAssign: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "fra",
|
||||||
|
name: "Frankfurt",
|
||||||
|
location: "Germany",
|
||||||
|
subdomain: "eu.tunnl.live",
|
||||||
|
coordinates: [8.6821, 50.1109],
|
||||||
|
ping: null,
|
||||||
|
status: "online",
|
||||||
|
pingStatus: "idle",
|
||||||
|
capabilities: {
|
||||||
|
http: true,
|
||||||
|
tcp: true,
|
||||||
|
},
|
||||||
|
portRestrictions: {
|
||||||
|
allowedRanges: [
|
||||||
|
{ min: 20000, max: 21000 },
|
||||||
|
],
|
||||||
|
blockedPorts: [8080, 8443, 9000],
|
||||||
|
supportsAutoAssign: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -73,8 +102,7 @@ const fetchServers = async (): Promise<Server[]> => {
|
|||||||
},
|
},
|
||||||
portRestrictions: {
|
portRestrictions: {
|
||||||
allowedRanges: [
|
allowedRanges: [
|
||||||
{ min: 8000, max: 8999 },
|
{ min: 20000, max: 21000 },
|
||||||
{ min: 9000, max: 9999 },
|
|
||||||
],
|
],
|
||||||
blockedPorts: [8080, 8443, 9000],
|
blockedPorts: [8080, 8443, 9000],
|
||||||
supportsAutoAssign: true,
|
supportsAutoAssign: true,
|
||||||
@@ -154,6 +182,7 @@ export default function TunnelConfig({
|
|||||||
selectedServer,
|
selectedServer,
|
||||||
onServerSelect,
|
onServerSelect,
|
||||||
isAuthenticated = false,
|
isAuthenticated = false,
|
||||||
|
userId,
|
||||||
}: TunnelConfigProps) {
|
}: TunnelConfigProps) {
|
||||||
const [localConfig, setLocalConfig] = useState<TunnelConfig>({
|
const [localConfig, setLocalConfig] = useState<TunnelConfig>({
|
||||||
...config,
|
...config,
|
||||||
@@ -167,7 +196,7 @@ export default function TunnelConfig({
|
|||||||
const [serverError, setServerError] = useState<string | null>(null)
|
const [serverError, setServerError] = useState<string | null>(null)
|
||||||
const [portError, setPortError] = useState<string | null>(null)
|
const [portError, setPortError] = useState<string | null>(null)
|
||||||
const [pendingServerSelection, setPendingServerSelection] = useState<Server | null>(null)
|
const [pendingServerSelection, setPendingServerSelection] = useState<Server | null>(null)
|
||||||
const [showTcpLoginPrompt, setShowTcpLoginPrompt] = useState(false)
|
const [showCustomDomainLoginPrompt, setShowCustomDomainLoginPrompt] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadServers = async () => {
|
const loadServers = async () => {
|
||||||
@@ -242,7 +271,7 @@ export default function TunnelConfig({
|
|||||||
s.ping !== null &&
|
s.ping !== null &&
|
||||||
s.capabilities.http &&
|
s.capabilities.http &&
|
||||||
((localConfig.type === "http" && s.capabilities.http) ||
|
((localConfig.type === "http" && s.capabilities.http) ||
|
||||||
(localConfig.type === "tcp" && s.capabilities.tcp && isAuthenticated)),
|
(localConfig.type === "tcp" && s.capabilities.tcp)),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (compatibleServers.length > 0) {
|
if (compatibleServers.length > 0) {
|
||||||
@@ -275,7 +304,7 @@ export default function TunnelConfig({
|
|||||||
onServerSelect(pendingServerSelection)
|
onServerSelect(pendingServerSelection)
|
||||||
setPendingServerSelection(null)
|
setPendingServerSelection(null)
|
||||||
|
|
||||||
if (localConfig.type === "tcp" && (!pendingServerSelection.capabilities.tcp || !isAuthenticated)) {
|
if (localConfig.type === "tcp" && !pendingServerSelection.capabilities.tcp) {
|
||||||
updateConfig({ type: "http", serverPort: 443 })
|
updateConfig({ type: "http", serverPort: 443 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,19 +355,24 @@ export default function TunnelConfig({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleTcpSelection = () => {
|
const handleTcpSelection = () => {
|
||||||
|
setShowCustomDomainLoginPrompt(false)
|
||||||
|
updateConfig({ type: "tcp", serverPort: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCustomDomainSelection = () => {
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
setShowTcpLoginPrompt(true)
|
setShowCustomDomainLoginPrompt(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updateConfig({ type: "tcp", serverPort: 0 })
|
window.location.href = "/custom-domain"
|
||||||
setShowTcpLoginPrompt(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateCommand = () => {
|
const generateCommand = () => {
|
||||||
if (!selectedServer) return ""
|
if (!selectedServer) return ""
|
||||||
const { serverPort, localPort } = localConfig
|
const { serverPort, localPort } = localConfig
|
||||||
return `ssh ${selectedServer.subdomain} -p 2200 -R ${serverPort}:localhost:${localPort}`
|
const target = isAuthenticated && userId ? `${userId}@${selectedServer.subdomain}` : selectedServer.subdomain
|
||||||
|
return `ssh ${target} -p 2200 -R ${serverPort}:localhost:${localPort}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyToClipboard = () => {
|
const copyToClipboard = () => {
|
||||||
@@ -480,7 +514,7 @@ export default function TunnelConfig({
|
|||||||
if (localConfig.type === "http" && !server.capabilities.http) {
|
if (localConfig.type === "http" && !server.capabilities.http) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (localConfig.type === "tcp" && (!server.capabilities.tcp || !isAuthenticated)) {
|
if (localConfig.type === "tcp" && !server.capabilities.tcp) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,9 +528,6 @@ export default function TunnelConfig({
|
|||||||
if (localConfig.type === "tcp" && !server.capabilities.tcp) {
|
if (localConfig.type === "tcp" && !server.capabilities.tcp) {
|
||||||
return "TCP not supported"
|
return "TCP not supported"
|
||||||
}
|
}
|
||||||
if (localConfig.type === "tcp" && !isAuthenticated) {
|
|
||||||
return "Sign in required for TCP"
|
|
||||||
}
|
|
||||||
if (localConfig.type === "http" && !server.capabilities.http) {
|
if (localConfig.type === "http" && !server.capabilities.http) {
|
||||||
return "HTTP not supported"
|
return "HTTP not supported"
|
||||||
}
|
}
|
||||||
@@ -506,7 +537,7 @@ export default function TunnelConfig({
|
|||||||
const getCompatibleServers = () => {
|
const getCompatibleServers = () => {
|
||||||
return servers.filter((server) => {
|
return servers.filter((server) => {
|
||||||
if (localConfig.type === "http") return server.capabilities.http
|
if (localConfig.type === "http") return server.capabilities.http
|
||||||
if (localConfig.type === "tcp") return server.capabilities.tcp && isAuthenticated
|
if (localConfig.type === "tcp") return server.capabilities.tcp
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -583,13 +614,10 @@ export default function TunnelConfig({
|
|||||||
</svg>
|
</svg>
|
||||||
<p className="text-yellow-400 font-medium">
|
<p className="text-yellow-400 font-medium">
|
||||||
No servers support {localConfig.type.toUpperCase()} forwarding
|
No servers support {localConfig.type.toUpperCase()} forwarding
|
||||||
{!isAuthenticated && localConfig.type === "tcp" && " for guest users"}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-yellow-300 text-sm">
|
<p className="text-yellow-300 text-sm">
|
||||||
{!isAuthenticated && localConfig.type === "tcp"
|
Please switch to HTTP/HTTPS forwarding or wait for compatible servers to come online.
|
||||||
? "Please sign in to access TCP forwarding or switch to HTTP/HTTPS forwarding."
|
|
||||||
: "Please switch to HTTP/HTTPS forwarding or wait for TCP-compatible servers to come online."}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -736,19 +764,17 @@ export default function TunnelConfig({
|
|||||||
onServerSelect(server)
|
onServerSelect(server)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`p-3 rounded-lg border transition-all duration-200 ${
|
className={`p-3 rounded-lg border transition-all duration-200 ${selectedServer?.id === server.id
|
||||||
selectedServer?.id === server.id
|
|
||||||
? "bg-emerald-950 border-emerald-500"
|
? "bg-emerald-950 border-emerald-500"
|
||||||
: !canSelect
|
: !canSelect
|
||||||
? "bg-red-950 border-red-800 cursor-not-allowed opacity-75"
|
? "bg-red-950 border-red-800 cursor-not-allowed opacity-75"
|
||||||
: "bg-gray-800 border-gray-700 hover:border-gray-600 cursor-pointer"
|
: "bg-gray-800 border-gray-700 hover:border-gray-600 cursor-pointer"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<h5 className="font-medium text-sm">{server.name}</h5>
|
<h5 className="font-medium text-sm">{server.name}</h5>
|
||||||
<div
|
<div
|
||||||
className={`w-2 h-2 rounded-full ${
|
className={`w-2 h-2 rounded-full ${selectedServer?.id === server.id
|
||||||
selectedServer?.id === server.id
|
|
||||||
? "bg-emerald-400"
|
? "bg-emerald-400"
|
||||||
: !canSelect
|
: !canSelect
|
||||||
? "bg-red-400"
|
? "bg-red-400"
|
||||||
@@ -761,7 +787,7 @@ export default function TunnelConfig({
|
|||||||
? "bg-orange-400"
|
? "bg-orange-400"
|
||||||
: "bg-red-400"
|
: "bg-red-400"
|
||||||
: "bg-gray-600"
|
: "bg-gray-600"
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-400 mb-1">{server.location}</p>
|
<p className="text-xs text-gray-400 mb-1">{server.location}</p>
|
||||||
@@ -774,18 +800,14 @@ export default function TunnelConfig({
|
|||||||
<span className="text-xs bg-blue-900 text-blue-300 px-1.5 py-0.5 rounded">HTTP</span>
|
<span className="text-xs bg-blue-900 text-blue-300 px-1.5 py-0.5 rounded">HTTP</span>
|
||||||
)}
|
)}
|
||||||
{server.capabilities.tcp && (
|
{server.capabilities.tcp && (
|
||||||
<span
|
<span className="text-xs bg-purple-900 text-purple-300 px-1.5 py-0.5 rounded">
|
||||||
className={`text-xs px-1.5 py-0.5 rounded ${
|
TCP
|
||||||
isAuthenticated ? "bg-purple-900 text-purple-300" : "bg-gray-700 text-gray-400"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
TCP{!isAuthenticated && " 🔒"}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{server.capabilities.tcp && isAuthenticated && (
|
{server.capabilities.tcp && (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<p className="text-xs text-gray-300">{getPortRestrictionInfo(server)}</p>
|
<p className="text-xs text-gray-300">{getPortRestrictionInfo(server)}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -833,22 +855,24 @@ export default function TunnelConfig({
|
|||||||
<>
|
<>
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className="block text-sm font-medium mb-3">Forwarding Type</label>
|
<label className="block text-sm font-medium mb-3">Forwarding Type</label>
|
||||||
<div className="flex gap-4">
|
<div className="flex flex-col gap-3 sm:flex-row sm:flex-wrap">
|
||||||
<label className="flex items-center cursor-pointer">
|
<label className="flex w-full items-center cursor-pointer sm:w-auto">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="forwardingType"
|
name="forwardingType"
|
||||||
value="http"
|
value="http"
|
||||||
checked={localConfig.type === "http"}
|
checked={localConfig.type === "http"}
|
||||||
onChange={() => updateConfig({ type: "http", serverPort: 443 })}
|
onChange={() => {
|
||||||
|
setShowCustomDomainLoginPrompt(false)
|
||||||
|
updateConfig({ type: "http", serverPort: 443 })
|
||||||
|
}}
|
||||||
className="sr-only"
|
className="sr-only"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-2 px-4 py-2 rounded-lg border transition-all ${
|
className={`flex w-full items-center gap-2 rounded-lg border px-4 py-2 transition-all sm:w-auto ${localConfig.type === "http"
|
||||||
localConfig.type === "http"
|
|
||||||
? "bg-emerald-950 border-emerald-500 text-emerald-400"
|
? "bg-emerald-950 border-emerald-500 text-emerald-400"
|
||||||
: "bg-gray-800 border-gray-700 text-gray-300 hover:border-gray-600"
|
: "bg-gray-800 border-gray-700 text-gray-300 hover:border-gray-600"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`w-2 h-2 rounded-full ${localConfig.type === "http" ? "bg-emerald-400" : "bg-gray-500"}`}
|
className={`w-2 h-2 rounded-full ${localConfig.type === "http" ? "bg-emerald-400" : "bg-gray-500"}`}
|
||||||
@@ -857,32 +881,50 @@ export default function TunnelConfig({
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label className="flex items-center cursor-pointer">
|
<label className="flex w-full items-center cursor-pointer sm:w-auto">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="forwardingType"
|
name="forwardingType"
|
||||||
value="tcp"
|
value="tcp"
|
||||||
checked={localConfig.type === "tcp"}
|
checked={localConfig.type === "tcp"}
|
||||||
onChange={handleTcpSelection}
|
onChange={handleTcpSelection}
|
||||||
disabled={!isAuthenticated && !hasTcpServers}
|
|
||||||
className="sr-only"
|
className="sr-only"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-2 px-4 py-2 rounded-lg border transition-all ${
|
className={`flex w-full items-center gap-2 rounded-lg border px-4 py-2 transition-all sm:w-auto ${localConfig.type === "tcp"
|
||||||
localConfig.type === "tcp"
|
|
||||||
? "bg-emerald-950 border-emerald-500 text-emerald-400"
|
? "bg-emerald-950 border-emerald-500 text-emerald-400"
|
||||||
: !isAuthenticated && hasTcpServers
|
: "bg-gray-800 border-gray-700 text-gray-300 hover:border-gray-600"
|
||||||
? "bg-gray-800 border-gray-700 text-gray-400 cursor-pointer hover:border-yellow-600"
|
}`}
|
||||||
: isAuthenticated && hasTcpServers
|
|
||||||
? "bg-gray-800 border-gray-700 text-gray-300 hover:border-gray-600"
|
|
||||||
: "bg-gray-800 border-gray-700 text-gray-500 cursor-not-allowed opacity-50"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`w-2 h-2 rounded-full ${localConfig.type === "tcp" ? "bg-emerald-400" : "bg-gray-500"}`}
|
className={`w-2 h-2 rounded-full ${localConfig.type === "tcp" ? "bg-emerald-400" : "bg-gray-500"}`}
|
||||||
/>
|
/>
|
||||||
<span className="font-medium">TCP</span>
|
<span className="font-medium">TCP</span>
|
||||||
{!isAuthenticated && hasTcpServers && (
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="flex w-full items-center cursor-pointer sm:w-auto">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="forwardingType"
|
||||||
|
value="custom-domain"
|
||||||
|
checked={localConfig.type === "custom-domain"}
|
||||||
|
onChange={handleCustomDomainSelection}
|
||||||
|
className="sr-only"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`flex w-full items-center gap-2 rounded-lg border px-4 py-2 transition-all sm:w-auto ${localConfig.type === "custom-domain"
|
||||||
|
? "bg-emerald-950 border-emerald-500 text-emerald-400"
|
||||||
|
: !isAuthenticated
|
||||||
|
? "bg-gray-800 border-gray-700 text-gray-400 cursor-pointer hover:border-yellow-600"
|
||||||
|
: "bg-gray-800 border-gray-700 text-gray-300 hover:border-gray-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`w-2 h-2 rounded-full ${localConfig.type === "custom-domain" ? "bg-emerald-400" : "bg-gray-500"}`}
|
||||||
|
/>
|
||||||
|
<span className="font-medium">Custom Domain</span>
|
||||||
|
{!isAuthenticated && (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="14"
|
width="14"
|
||||||
@@ -899,7 +941,7 @@ export default function TunnelConfig({
|
|||||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
{isAuthenticated && hasTcpServers && (
|
{isAuthenticated && (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="14"
|
width="14"
|
||||||
@@ -924,12 +966,14 @@ export default function TunnelConfig({
|
|||||||
<p className="text-sm text-gray-400 mt-2">
|
<p className="text-sm text-gray-400 mt-2">
|
||||||
{localConfig.type === "http"
|
{localConfig.type === "http"
|
||||||
? "Best for web applications and APIs. Uses HTTPS (port 443) or HTTP (port 80)."
|
? "Best for web applications and APIs. Uses HTTPS (port 443) or HTTP (port 80)."
|
||||||
: isAuthenticated
|
: localConfig.type === "tcp"
|
||||||
? "For any TCP service like databases, game servers, or custom applications."
|
? "For any TCP service like databases, game servers, or custom applications."
|
||||||
: "TCP forwarding requires authentication for security and abuse prevention."}
|
: !isAuthenticated
|
||||||
|
? "Use your own domain name for professional tunneling. Requires authentication."
|
||||||
|
: "Configure and use your own custom domain for tunneling."}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{showTcpLoginPrompt && !isAuthenticated && (
|
{showCustomDomainLoginPrompt && !isAuthenticated && (
|
||||||
<div className="mt-4 p-4 bg-blue-950 rounded-lg border border-blue-800">
|
<div className="mt-4 p-4 bg-blue-950 rounded-lg border border-blue-800">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<svg
|
<svg
|
||||||
@@ -944,20 +988,19 @@ export default function TunnelConfig({
|
|||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
className="text-blue-400 mt-0.5 flex-shrink-0"
|
className="text-blue-400 mt-0.5 flex-shrink-0"
|
||||||
>
|
>
|
||||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
<circle cx="12" cy="12" r="10" />
|
||||||
<circle cx="9" cy="7" r="4" />
|
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
||||||
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
|
<path d="M2 12h20" />
|
||||||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
||||||
</svg>
|
</svg>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h4 className="text-blue-400 font-medium mb-2">TCP Forwarding Requires Sign In</h4>
|
<h4 className="text-blue-400 font-medium mb-2">Custom Domain Requires Sign In</h4>
|
||||||
<p className="text-blue-300 text-sm mb-3">
|
<p className="text-blue-300 text-sm mb-3">
|
||||||
To prevent abuse and ensure service quality, TCP forwarding requires user authentication. This
|
Custom domain support allows you to use your own domain name for professional tunneling. This
|
||||||
helps us maintain a reliable service for everyone.
|
feature requires authentication to verify domain ownership and manage DNS settings.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-blue-300 text-sm mb-4">
|
<p className="text-blue-300 text-sm mb-4">
|
||||||
TCP forwarding allows you to tunnel any TCP-based service like databases, game servers, SSH, and
|
With a custom domain, you can create branded tunnel URLs like tunnel.yourdomain.com instead of
|
||||||
custom applications.
|
using our default subdomains.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Link
|
<Link
|
||||||
@@ -983,7 +1026,7 @@ export default function TunnelConfig({
|
|||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowTcpLoginPrompt(false)
|
setShowCustomDomainLoginPrompt(false)
|
||||||
updateConfig({ type: "http", serverPort: 443 })
|
updateConfig({ type: "http", serverPort: 443 })
|
||||||
}}
|
}}
|
||||||
className="text-blue-300 hover:text-blue-200 text-sm underline"
|
className="text-blue-300 hover:text-blue-200 text-sm underline"
|
||||||
@@ -997,7 +1040,7 @@ export default function TunnelConfig({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!showTcpLoginPrompt && (
|
{!showCustomDomainLoginPrompt && (
|
||||||
<div className="grid gap-4 md:grid-cols-2 mb-6">
|
<div className="grid gap-4 md:grid-cols-2 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium mb-2">Server Port (Internet Access)</label>
|
<label className="block text-sm font-medium mb-2">Server Port (Internet Access)</label>
|
||||||
@@ -1016,9 +1059,8 @@ export default function TunnelConfig({
|
|||||||
type="number"
|
type="number"
|
||||||
value={localConfig.serverPort === 0 ? "" : localConfig.serverPort}
|
value={localConfig.serverPort === 0 ? "" : localConfig.serverPort}
|
||||||
onChange={(e) => updateConfig({ serverPort: Number.parseInt(e.target.value) || 0 })}
|
onChange={(e) => updateConfig({ serverPort: Number.parseInt(e.target.value) || 0 })}
|
||||||
className={`w-full bg-gray-800 border rounded-lg px-3 py-2 text-white font-mono focus:outline-none ${
|
className={`w-full bg-gray-800 border rounded-lg px-3 py-2 text-white font-mono focus:outline-none ${portError ? "border-red-500 focus:border-red-400" : "border-gray-700 focus:border-emerald-500"
|
||||||
portError ? "border-red-500 focus:border-red-400" : "border-gray-700 focus:border-emerald-500"
|
}`}
|
||||||
}`}
|
|
||||||
placeholder="0 for auto-assign"
|
placeholder="0 for auto-assign"
|
||||||
min="0"
|
min="0"
|
||||||
max="65535"
|
max="65535"
|
||||||
@@ -1054,7 +1096,7 @@ export default function TunnelConfig({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!showTcpLoginPrompt && selectedServer && (
|
{!showCustomDomainLoginPrompt && selectedServer && (
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className="block text-sm font-medium mb-2">SSH Command</label>
|
<label className="block text-sm font-medium mb-2">SSH Command</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -1103,7 +1145,7 @@ export default function TunnelConfig({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!showTcpLoginPrompt && (
|
{!showCustomDomainLoginPrompt && (
|
||||||
<div className="p-3 bg-gray-800 rounded-lg border border-gray-700">
|
<div className="p-3 bg-gray-800 rounded-lg border border-gray-700">
|
||||||
<p className="text-sm text-gray-300">
|
<p className="text-sm text-gray-300">
|
||||||
<span className="font-medium">Traffic Flow:</span> Internet →{" "}
|
<span className="font-medium">Traffic Flow:</span> Internet →{" "}
|
||||||
|
|||||||
521
components/tunnl-logo.tsx
Normal file
521
components/tunnl-logo.tsx
Normal file
@@ -0,0 +1,521 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
export default function TunnlLogo() {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width={20} height={18}>
|
||||||
|
<path
|
||||||
|
d="M8.52 4c.296.02.574.086.863.164.09.027.18.05.27.074.066.02.066.02.132.035.29.079.578.149.871.211.332.075.66.153.989.235.492.125.984.246 1.48.355.344.078.684.16 1.027.246.11.028.215.051.325.078.402.098.8.196 1.203.297l.953.235c.023.008.05.011.074.02.16.038.32.073.48.109 1.305.285 1.305.285 1.688.52.031.015.059.034.09.054.262.168.422.37.586.637l.047.074c.238.414.187 1.015.187 1.48v1.649c0 .609-.023 1.144-.445 1.613-.39.367-.871.52-1.367.684-.059.02-.118.039-.18.062-.086.027-.168.059-.254.086-.176.059-.348.121-.523.184a8.12 8.12 0 0 1-.52.175c-.164.051-.324.114-.484.172-.102.04-.2.07-.301.102-.262.078-.52.168-.774.261-.652.231-1.308.458-1.964.68-.2.067-.399.133-.598.203a249.724 249.724 0 0 1-1.176.403c-.136.047-.273.093-.41.136-.05.02-.101.036-.156.055-.067.024-.137.047-.207.07-.04.012-.082.028-.121.04-.098.023-.098.023-.211-.016V12.69c.691-.175.691-.175.937-.23.028-.004.055-.012.086-.016.086-.02.176-.039.266-.058l.191-.04c.387-.085.774-.163 1.16-.238.356-.066.707-.144 1.059-.222.352-.078.703-.153 1.059-.215.355-.067.71-.137 1.062-.215.043-.008.086-.02.133-.027.515-.098.515-.098.918-.414.172-.278.152-.59.152-.91V9.98c.004-.09.004-.175.004-.261v-.657c.004-.039.004-.078.004-.117 0-.308-.05-.597-.203-.867-.067-.047-.067-.047-.149-.078l-.082-.04a.575.575 0 0 0-.086-.038l-.082-.04a1.447 1.447 0 0 0-.46-.093c-.036-.004-.07-.004-.106-.008-.039-.004-.074-.008-.113-.008a17.593 17.593 0 0 1-.766-.082c-.336-.043-.672-.062-1.012-.086-.261-.015-.52-.039-.777-.066-.344-.039-.687-.062-1.035-.082-.348-.023-.7-.05-1.047-.086-.332-.031-.668-.047-1-.062a4.771 4.771 0 0 1-.187-.61c-.207-.879-.653-1.832-1.356-2.41-.11-.098-.11-.098-.144-.207V4Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#f4f0ed",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.129 3.652c.035.016.035.016.07.035 1.043.481 1.867 1.415 2.285 2.5.27.743.27.743.196 1.083-.028-.004-.055-.004-.086-.008a49.636 49.636 0 0 0-1.864-.11c-.011-.02-.02-.043-.03-.066-.34-.82-.34-.82-.985-1.395v-.074l-.098-.035a3.2 3.2 0 0 1-.336-.14c-.62-.266-1.363-.243-1.988-.008-.79.332-1.242.941-1.586 1.722-.148.442-.129.934-.129 1.399 0 .066 0 .136-.004.207v.558c0 .2 0 .395-.004.59 0 .371-.004.738-.004 1.11 0 .421-.003.843-.003 1.265l-.012 2.598-.446.094c-.062.011-.062.011-.128.027-.86.176-.86.176-1.227.226-.09-.136-.086-.238-.086-.398V12.059c.004-.391 0-.778 0-1.164V8.53c0-.582.012-1.187.168-1.75.008-.031.016-.058.023-.09a4.958 4.958 0 0 1 1.36-2.23h.074l.04-.113h.108c.012-.024.02-.047.028-.07.058-.106.117-.137.219-.196.144-.086.144-.086.289-.176 1.195-.808 2.879-.836 4.156-.254Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#f2efec",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12.488 7.957h.14c.13 0 .259.012.392.023a51.951 51.951 0 0 0 .996.07l.53.036.884.059c.093.007.191.011.289.02.133.007.27.019.406.026.043.004.082.008.125.008.059.004.059.004.113.012.051 0 .051 0 .102.004.082.015.082.015.195.094v.074l.113.039c.047.101.043.176.043.289 0 .043.004.086.004.133 0 .203.004.402.004.605 0 .106 0 .211.004.317 0 .156 0 .308.004.46v.145c0 .328 0 .328-.105.484a1.149 1.149 0 0 1-.504.192l-.075.012-.234.035c-.055.008-.113.015-.168.027-.36.055-.723.11-1.082.16-.176.028-.355.051-.531.078-.028.004-.059.012-.086.016-.18.027-.36.055-.54.086-.105.02-.214.035-.323.055-.055.007-.106.02-.16.027-.079.012-.153.027-.231.039-.063.012-.063.012-.133.023-.25.02-.422-.02-.613-.183-.149-.2-.156-.395-.156-.637v-.156c0-.055-.004-.11-.004-.164V8.59c.011-.203.047-.352.175-.508.149-.129.231-.125.426-.125ZM14.523 9.5a.528.528 0 0 0 0 .379c.07.094.07.094.204.125.132.008.132.008.222-.063a.594.594 0 0 0 .098-.363c-.05-.117-.05-.117-.149-.195-.164-.028-.27-.012-.375.117Zm1.114-.078c-.09.11-.078.203-.078.344.007.09.007.09.09.156.128.02.128.02.261 0 .098-.082.11-.137.13-.27-.009-.117-.009-.117-.083-.207-.113-.082-.191-.078-.32-.023Zm-2.317.156c-.043.125-.062.211-.008.34.051.074.051.074.16.16a.518.518 0 0 0 .352-.101c.086-.133.09-.247.059-.399-.055-.101-.055-.101-.149-.156-.187-.031-.289.012-.414.156Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#5aa680",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M9.68 12.77v2.808c-.293.129-.293.129-.414.172-.024.008-.051.016-.079.027-.027.008-.054.02-.082.028l-.09.03a3.392 3.392 0 0 1-.183.063c-.062.024-.125.047-.187.067-.09.031-.18.066-.274.098-.027.007-.055.019-.082.027a.613.613 0 0 1-.41.027v-2.965a7.29 7.29 0 0 1 .695-.183c.028-.008.059-.012.086-.02l.18-.035c.09-.02.18-.035.27-.055l.175-.035c.024-.004.05-.011.078-.015.11-.024.207-.04.317-.04Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#f4f0ee",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6.703 9.59h3.133c.164.004.234.008.371.117.074.102.074.102.098.219-.024.113-.024.113-.098.195-.16.098-.289.094-.473.09H7.082c-.113 0-.23-.004-.344-.004h-.101c-.176 0-.305-.008-.446-.129-.043-.133-.043-.133-.039-.27.157-.222.305-.222.551-.218Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#f7f5f2",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8.145 10.914c.027 0 .058 0 .09-.004h2.097c.094-.004.188 0 .281 0 .043 0 .043 0 .086-.004a.755.755 0 0 1 .446.133c.074.113.074.113.07.25-.031.133-.031.133-.09.2-.133.07-.258.066-.402.066h-.094c-.106 0-.207 0-.313-.004H8.031c-.152-.004-.281-.012-.414-.09-.054-.125-.054-.125-.074-.27.043-.12.078-.171.191-.234.137-.043.266-.047.41-.043Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#edeae7",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8.555 8.324H9.949c.13-.004.254-.004.38-.004h.577c.16.032.215.09.313.22.004.12.004.12-.035.23-.09.093-.145.113-.27.128h-.144c-.04.004-.04.004-.079.004H9.02c-.06 0-.118 0-.176.004-.086 0-.168 0-.25-.004h-.145c-.14-.02-.183-.054-.27-.172-.015-.125-.015-.199.044-.312.105-.105.187-.09.332-.094Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#eeebe9",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M10.094 9.77c.062.011.125.023.187.039a.757.757 0 0 1 0 .23.431.431 0 0 1-.265.168c-.094.004-.188.004-.282.004H9.02c-.176 0-.348 0-.52-.004H7.074c-.117-.004-.23-.004-.344-.004H6.45c-.07-.012-.07-.012-.183-.086l3.828-.039v-.117h-.074v-.078h.074V9.77Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#9d9c9b",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8.52 4c.335.016.648.105.972.191v.04h-.336c-.004.042-.011.085-.015.128-.024.141-.024.141-.098.22.05.023.098.05.148.073-.023.04-.046.078-.074.118-.031-.036-.058-.07-.094-.106-.039-.043-.082-.09-.12-.137l-.063-.066c-.02-.024-.04-.043-.059-.066-.02-.024-.039-.043-.058-.067a2.522 2.522 0 0 0-.145-.144C8.52 4.117 8.52 4.117 8.52 4Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#d2cfce",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6.34 9.617c.488-.004.976-.008 1.46-.008.227 0 .454 0 .68-.004h.653c.082 0 .168 0 .25-.003H9.836c.234 0 .234 0 .355.074.028.027.028.027.051.054l-.035.079c-.05-.012-.102-.028-.152-.04v-.078c-1.223-.011-2.45-.023-3.715-.039v-.035Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#d9d5d6",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m13.707 11.324.102.012.074.012c-.2.117-.434.129-.66.164-.082.015-.164.027-.246.043l-.16.023-.145.024c-.14.015-.262 0-.402-.024l-.036-.117c.07.004.07.004.145.004.195 0 .383-.027.574-.059.035-.004.067-.011.102-.015.125-.02.246-.04.37-.07a.97.97 0 0 1 .282.003Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#589275",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8.145 10.914h2.558c.152 0 .27.016.402.086-.14.047-.273.043-.421.043H9.473c-.196-.004-.391-.004-.586-.004H7.73c-.011.04-.027.074-.039.113a1.343 1.343 0 0 0-.074-.035c.04-.117.04-.117.113-.164.141-.039.27-.039.415-.039Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#c5c4c3",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M11.145 8.46c.035.04.035.04.074.08-.004.124-.012.202-.098.292-.121.07-.215.066-.351.066H9.387c-.125 0-.25 0-.375-.003H8.44c-.109-.012-.109-.012-.187-.086h2.742c.004-.082.004-.082-.039-.157h.148c.016-.062.028-.125.04-.191Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#bbbab9",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.953 13.117c.027.012.05.024.078.035-.039.016-.074.028-.113.04l.035 2.886c.102-.015.2-.027.3-.039-.1.106-.19.102-.335.113-.063-.062-.043-.144-.043-.23v-.657c0-.175 0-.35.004-.527v-1.585c.027-.012.05-.024.074-.036Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#999896",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M19.7 7.809h.038V9.96H19.7L19.664 8.5h-.039v-.23c.012-.012.023-.028.04-.04.007-.07.015-.14.019-.214.003-.04.007-.079.007-.118.004-.03.008-.058.008-.09Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#ebe6e6",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M13.207 8c.422.023.844.05 1.277.078-.011.035-.023.074-.039.113h-.71c-.012-.039-.028-.074-.04-.113h-.488V8Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#5d8d79",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12.469 7.957h.12a.753.753 0 0 0 .095.004c.011.027.023.05.035.078-.114.04-.192.04-.309.043-.125.008-.195.016-.293.098a2.451 2.451 0 0 0-.148.203 4.044 4.044 0 0 1-.035-.113c.14-.262.25-.32.535-.313Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#5b957c",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15.57 8.191h.309c.031 0 .059-.004.09-.004.148 0 .285.004.43.043v.079c-.278.004-.551.004-.829-.04v-.078Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#66a686",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12.234 11.46h.75v.08c-.097.01-.195.026-.293.038l-.086.012a.654.654 0 0 1-.335-.012l-.036-.117Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#5e9b78",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M.715 14.23H.75c.04.31.043.614.04.922-.028-.011-.052-.023-.075-.035l-.024-.402c0-.035-.003-.074-.007-.113 0-.055 0-.055-.004-.11 0-.035-.004-.066-.004-.101 0-.082 0-.082.039-.16Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#d1d0ce",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M10.094 9.77c.062.011.125.023.187.039v.23l-.148.078a1.826 1.826 0 0 1-.04-.156h-.073v-.078h.074V9.77Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#e1dfde",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M9.457 9.617c.102-.004.207-.008.309-.008.027-.004.058-.004.09-.004.218-.003.218-.003.332.067.019.02.039.039.054.058l-.035.079c-.05-.012-.102-.028-.152-.04v-.078c-.024 0-.047 0-.075.004a2.35 2.35 0 0 1-.523-.043v-.035Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#cccbcb",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m16.398 8.23.223.04c.016.039.027.074.04.113.038.015.073.027.112.039l.036.23c-.11-.035-.11-.035-.164-.129-.07-.117-.122-.14-.247-.175V8.23Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#598871",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15.309 11.04c.136.01.273.023.414.038v.04a3.446 3.446 0 0 1-.825.073c.09-.09.125-.09.247-.101a.758.758 0 0 0 .093-.008c.024 0 .047-.004.07-.004v-.039Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#528068",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m14.488 11.21.106.013c.023 0 .05.004.078.007v.04c-.266.05-.516.085-.79.078v-.04c.024-.007.048-.011.071-.015a.8.8 0 0 1 .094-.02l.094-.023c.222-.055.222-.055.347-.04Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#54856a",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M13.883 8.078h.601c-.011.035-.023.074-.039.113l-.218-.011-.122-.012c-.109-.016-.109-.016-.222-.09Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#59997b",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M13.734 5.27c.23-.016.418.039.637.113-.113.039-.144.043-.262 0v.078h-.113v-.078h-.074v-.074h-.188v-.04Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#b4b1b1",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.879 15.617h.039l.035.461c.102-.015.2-.027.3-.039-.1.106-.19.102-.335.113-.04-.035-.04-.035-.043-.148 0-.047.004-.094.004-.14v-.247Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#8f8e8d",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M17.074 10.809a.325.325 0 0 1 .035.152.746.746 0 0 1-.261.309c-.035-.012-.075-.028-.114-.04l.086-.058c.121-.113.176-.215.254-.363Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#a5a4a2",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M19.7 7.809h.038v.652c-.039.012-.074.027-.113.039v-.23c.012-.012.023-.028.04-.04.007-.07.015-.14.019-.214.003-.04.007-.079.007-.118.004-.03.008-.058.008-.09Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#d9d6d5",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.656 3.96c.219.106.219.106.262.192h-.113c-.012.051-.024.102-.04.157L7.73 4.19a.445.445 0 0 1-.074-.039v-.191Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#cac8c6",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8.707 15.883a.647.647 0 0 1-.289.12.8.8 0 0 1-.094.02c-.023.004-.047.012-.07.016.082-.25.242-.187.453-.156Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#bbb4b2",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M10.094 15.23c.074.028.148.051.226.079.024-.028.047-.051.075-.079.05.028.097.051.148.079-.05.02-.105.039-.16.058-.047.016-.047.016-.09.035-.086.02-.086.02-.2-.02v-.152Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#b3b1b0",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M16.059 11.46c-.02.013-.043.02-.067.032-.082.04-.082.04-.133.11-.062.05-.062.05-.148.046-.027-.007-.059-.015-.09-.027l-.09-.023c-.035-.012-.035-.012-.07-.02v-.039c.2-.047.39-.09.598-.078Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#acaba9",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6.266 9.652h.074c.015.051.027.102.039.157-.074.039-.074.039-.149.074l.036.234c-.082-.07-.11-.101-.125-.21a.395.395 0 0 1 .125-.255Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#a8a7a8",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.094 5.809c.035.011.074.023.11.039v.074c.026.016.05.027.077.039a.728.728 0 0 0-.039.078c.016.082.016.082.04.152a1.678 1.678 0 0 1-.266-.308c.027-.024.05-.051.078-.074Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#c2c0bb",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M11.145 8.46c.023.028.05.052.074.08-.016.21-.016.21-.114.308-.035-.016-.074-.028-.109-.04V8.73a.486.486 0 0 1-.039-.078h.148c.016-.062.028-.125.04-.191Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#c5c4c2",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M16.246 7.77h.563c-.036.011-.075.023-.11.039v.113l-.453-.113v-.04Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#b8b7b5",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M2.516 4.191c.023.012.046.028.074.04-.027.062-.05.128-.074.19h-.114c-.011.028-.027.052-.039.079-.023-.012-.05-.023-.074-.04.012-.038.023-.073.04-.112h.108c.028-.051.051-.102.079-.157Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#a8a8a6",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m3.64 3.578.075.04c-.024.01-.047.023-.074.034a.717.717 0 0 1-.094.051.717.717 0 0 1-.094.05c-.086.052-.086.052-.113.169-.012-.035-.024-.074-.04-.113-.023-.012-.046-.028-.073-.04.066-.035.136-.066.203-.097.035-.02.074-.04.113-.055a.727.727 0 0 1 .098-.039Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#a3a2a2",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M1.238 15c.125.012.246.023.375.04v.038c-.199.024-.394.05-.601.074.113-.074.113-.074.226-.074V15Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#999998",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M3.527 5.73h.075c-.012.125-.04.157-.133.243-.031.02-.059.043-.09.066v-.156h.074c.024-.051.047-.102.074-.153Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#bbbab9",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m6.004 3.348.18.011.097.008c.098.016.098.016.246.094h-.523v-.113Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#b0afac",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M9.367 15.613h.094c.035 0 .035 0 .07.004-.129.086-.222.098-.375.113-.011-.023-.023-.05-.039-.078.09-.043.149-.039.25-.039Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#a9a6a4",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15.984 9.77c.012.023.024.05.04.078-.036.082-.036.082-.075.152h-.226c-.012-.023-.024-.05-.04-.078l.11-.012c.129-.012.129-.012.191-.14Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#4e7d64",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12.082 8.04c.023.01.05.023.074.038-.14.262-.14.262-.222.305a.595.595 0 0 1 .148-.344Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#51836d",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12.047 14.578c.168-.015.168-.015.254.055.015.02.027.039.043.058-.086.028-.172.051-.262.079.012-.051.023-.102.04-.153l-.075-.039Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#aea9a6",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M17.86 12.77c-.13.09-.223.097-.376.113.028-.012.051-.024.078-.035v-.078c.106-.055.184-.024.297 0Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#b4b1b0",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15.496 9.46h.074l-.035.349h-.074c-.031-.114-.047-.172-.004-.282.012-.023.027-.043.04-.066Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#74b698",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m16.059 9.422-.075.117c-.015-.02-.03-.035-.046-.055a.72.72 0 0 0-.215-.136c.148-.063.21-.012.336.074Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#53866a",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M16.809 8c.05.012.101.023.152.04v.077h.113l.035.153c-.035.02-.035.02-.074.039-.113-.145-.113-.145-.113-.23-.035-.017-.074-.028-.113-.04V8Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#afadab",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.953 10.96c-.035.013-.07.02-.11.032a3.353 3.353 0 0 1-.113.047c-.011.04-.027.074-.039.113a1.343 1.343 0 0 0-.074-.035c.04-.117.04-.117.125-.164.102-.031.102-.031.211.008Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#979696",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M9.906 6.152c.149.078.149.078.227.157l-.078.039c-.024.05-.047.101-.075.152-.023-.113-.046-.227-.074-.348Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#b7b5b5",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M10.133 4.383c.152.02.277.035.41.117-.121.027-.121.027-.262.04-.09-.075-.09-.075-.148-.157Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#bab8b7",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8.594 4.04c.035.01.074.022.113.038.023.078.023.078.035.152-.113 0-.113 0-.187-.039.011-.05.027-.101.039-.152Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#d3d1d1",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15.309 11.04c.136.01.273.023.414.038v.04c-.137.01-.274.023-.414.034v-.113Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#3f7154",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M13.695 9.383v.039c-.039.012-.082.023-.125.035-.136.035-.136.035-.25.121v-.156c.13-.04.242-.04.375-.04Zm0 0"
|
||||||
|
style={{
|
||||||
|
stroke: "none",
|
||||||
|
fillRule: "nonzero",
|
||||||
|
fill: "#3e6854",
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defineConfig } from "drizzle-kit";
|
import { defineConfig } from "drizzle-kit";
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
dialect: "postgresql",
|
dialect: "postgresql",
|
||||||
schema: "./app/db/schema/*",
|
schema: "./lib/schema/*",
|
||||||
out: "./drizzle",
|
out: "./drizzle",
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
url: process.env.DATABASE_URL!
|
url: process.env.DATABASE_URL!
|
||||||
|
|||||||
50
drizzle/0000_volatile_guardian.sql
Normal file
50
drizzle/0000_volatile_guardian.sql
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
CREATE TABLE "account" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"account_id" text NOT NULL,
|
||||||
|
"provider_id" text NOT NULL,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"access_token" text,
|
||||||
|
"refresh_token" text,
|
||||||
|
"id_token" text,
|
||||||
|
"access_token_expires_at" timestamp,
|
||||||
|
"refresh_token_expires_at" timestamp,
|
||||||
|
"scope" text,
|
||||||
|
"password" text,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "session" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"expires_at" timestamp NOT NULL,
|
||||||
|
"token" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp NOT NULL,
|
||||||
|
"ip_address" text,
|
||||||
|
"user_agent" text,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
CONSTRAINT "session_token_unique" UNIQUE("token")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "user" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"email" text NOT NULL,
|
||||||
|
"email_verified" boolean DEFAULT false NOT NULL,
|
||||||
|
"image" text,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "user_email_unique" UNIQUE("email")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "verification" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"identifier" text NOT NULL,
|
||||||
|
"value" text NOT NULL,
|
||||||
|
"expires_at" timestamp NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
326
drizzle/meta/0000_snapshot.json
Normal file
326
drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
{
|
||||||
|
"id": "be4362e0-2820-4d0f-b7f2-281416bccdd2",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.account": {
|
||||||
|
"name": "account",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"account_id": {
|
||||||
|
"name": "account_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"provider_id": {
|
||||||
|
"name": "provider_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"access_token": {
|
||||||
|
"name": "access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"name": "refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"id_token": {
|
||||||
|
"name": "id_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"access_token_expires_at": {
|
||||||
|
"name": "access_token_expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"refresh_token_expires_at": {
|
||||||
|
"name": "refresh_token_expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"name": "scope",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "password",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"account_user_id_user_id_fk": {
|
||||||
|
"name": "account_user_id_user_id_fk",
|
||||||
|
"tableFrom": "account",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.session": {
|
||||||
|
"name": "session",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"ip_address": {
|
||||||
|
"name": "ip_address",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"user_agent": {
|
||||||
|
"name": "user_agent",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"session_user_id_user_id_fk": {
|
||||||
|
"name": "session_user_id_user_id_fk",
|
||||||
|
"tableFrom": "session",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"session_token_unique": {
|
||||||
|
"name": "session_token_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"token"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.user": {
|
||||||
|
"name": "user",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"email_verified": {
|
||||||
|
"name": "email_verified",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"name": "image",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"user_email_unique": {
|
||||||
|
"name": "user_email_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.verification": {
|
||||||
|
"name": "verification",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"identifier": {
|
||||||
|
"name": "identifier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1767088616990,
|
||||||
|
"tag": "0000_volatile_guardian",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import { createAuthClient } from "better-auth/react"
|
import { createAuthClient } from "better-auth/react"
|
||||||
|
import { jwtClient } from "better-auth/client/plugins"
|
||||||
|
import { inferAdditionalFields } from "better-auth/client/plugins";
|
||||||
|
import { auth } from "@/lib/auth"
|
||||||
|
|
||||||
export const authClient = createAuthClient({
|
export const authClient = createAuthClient({
|
||||||
/** The base URL of the server (optional if you're using the same domain) */
|
plugins: [jwtClient(), inferAdditionalFields<typeof auth>()],
|
||||||
baseURL: "http://localhost:3000"
|
|
||||||
})
|
})
|
||||||
11
lib/auth.ts
11
lib/auth.ts
@@ -2,14 +2,25 @@ import { betterAuth } from "better-auth";
|
|||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import * as schema from "@/lib/schema/auth"
|
import * as schema from "@/lib/schema/auth"
|
||||||
|
import { jwt } from "better-auth/plugins";
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
|
plugins: [jwt()],
|
||||||
socialProviders: {
|
socialProviders: {
|
||||||
google: {
|
google: {
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID!,
|
clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
user: {
|
||||||
|
additionalFields: {
|
||||||
|
sshIdentifier: {
|
||||||
|
type: "string",
|
||||||
|
nullable: false,
|
||||||
|
input: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
database: drizzleAdapter(db, {
|
database: drizzleAdapter(db, {
|
||||||
provider: "pg",
|
provider: "pg",
|
||||||
schema: schema
|
schema: schema
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core";
|
import { relations, sql } from "drizzle-orm";
|
||||||
|
import { pgTable, text, timestamp, boolean, index } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const user = pgTable("user", {
|
export const user = pgTable("user", {
|
||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
|
sshIdentifier: text("ssh_identifier")
|
||||||
|
.notNull()
|
||||||
|
.unique()
|
||||||
|
.default(
|
||||||
|
sql`substr(encode(gen_random_bytes(16), 'hex'), 1, 32)`
|
||||||
|
),
|
||||||
email: text("email").notNull().unique(),
|
email: text("email").notNull().unique(),
|
||||||
emailVerified: boolean("email_verified").default(false).notNull(),
|
emailVerified: boolean("email_verified").default(false).notNull(),
|
||||||
image: text("image"),
|
image: text("image"),
|
||||||
@@ -13,49 +20,88 @@ export const user = pgTable("user", {
|
|||||||
.notNull(),
|
.notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const session = pgTable("session", {
|
export const session = pgTable(
|
||||||
|
"session",
|
||||||
|
{
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
expiresAt: timestamp("expires_at").notNull(),
|
||||||
|
token: text("token").notNull().unique(),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at")
|
||||||
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||||
|
.notNull(),
|
||||||
|
ipAddress: text("ip_address"),
|
||||||
|
userAgent: text("user_agent"),
|
||||||
|
userId: text("user_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, { onDelete: "cascade" }),
|
||||||
|
},
|
||||||
|
(table) => [index("session_userId_idx").on(table.userId)],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const account = pgTable(
|
||||||
|
"account",
|
||||||
|
{
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
accountId: text("account_id").notNull(),
|
||||||
|
providerId: text("provider_id").notNull(),
|
||||||
|
userId: text("user_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, { onDelete: "cascade" }),
|
||||||
|
accessToken: text("access_token"),
|
||||||
|
refreshToken: text("refresh_token"),
|
||||||
|
idToken: text("id_token"),
|
||||||
|
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
||||||
|
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
||||||
|
scope: text("scope"),
|
||||||
|
password: text("password"),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at")
|
||||||
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(table) => [index("account_userId_idx").on(table.userId)],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const verification = pgTable(
|
||||||
|
"verification",
|
||||||
|
{
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
identifier: text("identifier").notNull(),
|
||||||
|
value: text("value").notNull(),
|
||||||
|
expiresAt: timestamp("expires_at").notNull(),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at")
|
||||||
|
.defaultNow()
|
||||||
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(table) => [index("verification_identifier_idx").on(table.identifier)],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const jwks = pgTable("jwks", {
|
||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
expiresAt: timestamp("expires_at").notNull(),
|
publicKey: text("public_key").notNull(),
|
||||||
token: text("token").notNull().unique(),
|
privateKey: text("private_key").notNull(),
|
||||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
createdAt: timestamp("created_at").notNull(),
|
||||||
updatedAt: timestamp("updated_at")
|
expiresAt: timestamp("expires_at"),
|
||||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
||||||
.notNull(),
|
|
||||||
ipAddress: text("ip_address"),
|
|
||||||
userAgent: text("user_agent"),
|
|
||||||
userId: text("user_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => user.id, { onDelete: "cascade" }),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const account = pgTable("account", {
|
export const userRelations = relations(user, ({ many }) => ({
|
||||||
id: text("id").primaryKey(),
|
sessions: many(session),
|
||||||
accountId: text("account_id").notNull(),
|
accounts: many(account),
|
||||||
providerId: text("provider_id").notNull(),
|
}));
|
||||||
userId: text("user_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => user.id, { onDelete: "cascade" }),
|
|
||||||
accessToken: text("access_token"),
|
|
||||||
refreshToken: text("refresh_token"),
|
|
||||||
idToken: text("id_token"),
|
|
||||||
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
|
||||||
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
|
||||||
scope: text("scope"),
|
|
||||||
password: text("password"),
|
|
||||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
||||||
updatedAt: timestamp("updated_at")
|
|
||||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
||||||
.notNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const verification = pgTable("verification", {
|
export const sessionRelations = relations(session, ({ one }) => ({
|
||||||
id: text("id").primaryKey(),
|
user: one(user, {
|
||||||
identifier: text("identifier").notNull(),
|
fields: [session.userId],
|
||||||
value: text("value").notNull(),
|
references: [user.id],
|
||||||
expiresAt: timestamp("expires_at").notNull(),
|
}),
|
||||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
}));
|
||||||
updatedAt: timestamp("updated_at")
|
|
||||||
.defaultNow()
|
export const accountRelations = relations(account, ({ one }) => ({
|
||||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
user: one(user, {
|
||||||
.notNull(),
|
fields: [account.userId],
|
||||||
});
|
references: [user.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { NextConfig } from "next";
|
|||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
/* config options here */
|
||||||
|
output: "standalone",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
329
package-lock.json
generated
329
package-lock.json
generated
@@ -10,9 +10,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-auth": "^1.3.23",
|
"better-auth": "^1.3.23",
|
||||||
"dotenv": "^17.2.2",
|
"dotenv": "^17.2.2",
|
||||||
"drizzle-orm": "^0.44.5",
|
"drizzle-orm": "^0.45.0",
|
||||||
"net": "^1.0.2",
|
"net": "^1.0.2",
|
||||||
"next": "^15.5.2",
|
"next": "^16.0.0",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@@ -43,36 +43,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@better-auth/core": {
|
|
||||||
"version": "1.4.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.4.9.tgz",
|
|
||||||
"integrity": "sha512-JT2q4NDkQzN22KclUEoZ7qU6tl9HUTfK1ctg2oWlT87SEagkwJcnrUwS9VznL+u9ziOIfY27P0f7/jSnmvLcoQ==",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@standard-schema/spec": "^1.0.0",
|
|
||||||
"zod": "^4.1.12"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@better-auth/utils": "0.3.0",
|
|
||||||
"@better-fetch/fetch": "1.1.21",
|
|
||||||
"better-call": "1.1.7",
|
|
||||||
"jose": "^6.1.0",
|
|
||||||
"kysely": "^0.28.5",
|
|
||||||
"nanostores": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@better-auth/telemetry": {
|
|
||||||
"version": "1.4.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.4.9.tgz",
|
|
||||||
"integrity": "sha512-Tthy1/Gmx+pYlbvRQPBTKfVei8+pJwvH1NZp+5SbhwA6K2EXIaoonx/K6N/AXYs2aKUpyR4/gzqDesDjL7zd6A==",
|
|
||||||
"dependencies": {
|
|
||||||
"@better-auth/utils": "0.3.0",
|
|
||||||
"@better-fetch/fetch": "1.1.21"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@better-auth/core": "1.4.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@better-auth/utils": {
|
"node_modules/@better-auth/utils": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.3.0.tgz",
|
||||||
@@ -1484,16 +1454,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.1.tgz",
|
||||||
|
"integrity": "sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.1.tgz",
|
||||||
"integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==",
|
"integrity": "sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
@@ -1503,12 +1476,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.1.tgz",
|
||||||
"integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==",
|
"integrity": "sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
@@ -1518,12 +1492,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.1.tgz",
|
||||||
"integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==",
|
"integrity": "sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@@ -1533,12 +1508,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.1.tgz",
|
||||||
"integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==",
|
"integrity": "sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@@ -1548,12 +1524,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.1.tgz",
|
||||||
"integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==",
|
"integrity": "sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@@ -1563,12 +1540,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.1.tgz",
|
||||||
"integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==",
|
"integrity": "sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@@ -1578,12 +1556,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.1.tgz",
|
||||||
"integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==",
|
"integrity": "sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@@ -1593,7 +1572,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.1.tgz",
|
||||||
|
"integrity": "sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1644,7 +1625,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/node": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
|
||||||
|
"integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1654,35 +1637,37 @@
|
|||||||
"lightningcss": "1.30.2",
|
"lightningcss": "1.30.2",
|
||||||
"magic-string": "^0.30.21",
|
"magic-string": "^0.30.21",
|
||||||
"source-map-js": "^1.2.1",
|
"source-map-js": "^1.2.1",
|
||||||
"tailwindcss": "4.1.17"
|
"tailwindcss": "4.1.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide": {
|
"node_modules/@tailwindcss/oxide": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
|
||||||
|
"integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@tailwindcss/oxide-android-arm64": "4.1.17",
|
"@tailwindcss/oxide-android-arm64": "4.1.18",
|
||||||
"@tailwindcss/oxide-darwin-arm64": "4.1.17",
|
"@tailwindcss/oxide-darwin-arm64": "4.1.18",
|
||||||
"@tailwindcss/oxide-darwin-x64": "4.1.17",
|
"@tailwindcss/oxide-darwin-x64": "4.1.18",
|
||||||
"@tailwindcss/oxide-freebsd-x64": "4.1.17",
|
"@tailwindcss/oxide-freebsd-x64": "4.1.18",
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17",
|
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.17",
|
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": "4.1.17",
|
"@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": "4.1.17",
|
"@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
|
||||||
"@tailwindcss/oxide-linux-x64-musl": "4.1.17",
|
"@tailwindcss/oxide-linux-x64-musl": "4.1.18",
|
||||||
"@tailwindcss/oxide-wasm32-wasi": "4.1.17",
|
"@tailwindcss/oxide-wasm32-wasi": "4.1.18",
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.17",
|
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": "4.1.17"
|
"@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
|
||||||
"integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==",
|
"integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1697,9 +1682,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
|
||||||
"integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==",
|
"integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1714,9 +1699,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
|
||||||
"integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==",
|
"integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1731,9 +1716,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
|
||||||
"integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==",
|
"integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1748,9 +1733,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
|
||||||
"integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==",
|
"integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -1765,9 +1750,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
|
||||||
"integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==",
|
"integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1782,9 +1767,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
|
||||||
"integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==",
|
"integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1799,9 +1784,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
|
||||||
"integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==",
|
"integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1816,9 +1801,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
|
||||||
"integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==",
|
"integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1833,9 +1818,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
|
||||||
"integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==",
|
"integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
|
||||||
"bundleDependencies": [
|
"bundleDependencies": [
|
||||||
"@napi-rs/wasm-runtime",
|
"@napi-rs/wasm-runtime",
|
||||||
"@emnapi/core",
|
"@emnapi/core",
|
||||||
@@ -1851,10 +1836,10 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emnapi/core": "^1.6.0",
|
"@emnapi/core": "^1.7.1",
|
||||||
"@emnapi/runtime": "^1.6.0",
|
"@emnapi/runtime": "^1.7.1",
|
||||||
"@emnapi/wasi-threads": "^1.1.0",
|
"@emnapi/wasi-threads": "^1.1.0",
|
||||||
"@napi-rs/wasm-runtime": "^1.0.7",
|
"@napi-rs/wasm-runtime": "^1.1.0",
|
||||||
"@tybys/wasm-util": "^0.10.1",
|
"@tybys/wasm-util": "^0.10.1",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
},
|
},
|
||||||
@@ -1863,7 +1848,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||||
"version": "1.6.0",
|
"version": "1.7.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -1874,7 +1859,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||||
"version": "1.6.0",
|
"version": "1.7.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -1894,14 +1879,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||||
"version": "1.0.7",
|
"version": "1.1.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emnapi/core": "^1.5.0",
|
"@emnapi/core": "^1.7.1",
|
||||||
"@emnapi/runtime": "^1.5.0",
|
"@emnapi/runtime": "^1.7.1",
|
||||||
"@tybys/wasm-util": "^0.10.1"
|
"@tybys/wasm-util": "^0.10.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1923,9 +1908,9 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
|
||||||
"integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==",
|
"integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1940,7 +1925,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
|
||||||
|
"integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1955,15 +1942,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/postcss": {
|
"node_modules/@tailwindcss/postcss": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz",
|
||||||
|
"integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
"@tailwindcss/node": "4.1.17",
|
"@tailwindcss/node": "4.1.18",
|
||||||
"@tailwindcss/oxide": "4.1.17",
|
"@tailwindcss/oxide": "4.1.18",
|
||||||
"postcss": "^8.4.41",
|
"postcss": "^8.4.41",
|
||||||
"tailwindcss": "4.1.17"
|
"tailwindcss": "4.1.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/d3-color": {
|
"node_modules/@types/d3-color": {
|
||||||
@@ -2007,9 +1996,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.10.4",
|
"version": "24.10.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.7.tgz",
|
||||||
"integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==",
|
"integrity": "sha512-+054pVMzVTmRQV8BhpGv3UyfZ2Llgl8rdpDTon+cUH9+na0ncBVXj3wTUKh14+Kiz18ziM3b4ikpP5/Pc0rQEQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2030,7 +2019,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.2.7",
|
"version": "19.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz",
|
||||||
|
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -2057,14 +2048,23 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/baseline-browser-mapping": {
|
||||||
|
"version": "2.9.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
|
||||||
|
"integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"baseline-browser-mapping": "dist/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/better-auth": {
|
"node_modules/better-auth": {
|
||||||
"version": "1.4.9",
|
"version": "1.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.4.10.tgz",
|
||||||
"integrity": "sha512-usSdjuyTzZwIvM8fjF8YGhPncxV3MAg3dHUO9uPUnf0yklXUSYISiH1+imk6/Z+UBqsscyyPRnbIyjyK97p7YA==",
|
"integrity": "sha512-0kqwEBJLe8eyFzbUspRG/htOriCf9uMLlnpe34dlIJGdmDfPuQISd4shShvUrvIVhPxsY1dSTXdXPLpqISYOYg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@better-auth/core": "1.4.9",
|
"@better-auth/core": "1.4.10",
|
||||||
"@better-auth/telemetry": "1.4.9",
|
"@better-auth/telemetry": "1.4.10",
|
||||||
"@better-auth/utils": "0.3.0",
|
"@better-auth/utils": "0.3.0",
|
||||||
"@better-fetch/fetch": "1.1.21",
|
"@better-fetch/fetch": "1.1.21",
|
||||||
"@noble/ciphers": "^2.0.0",
|
"@noble/ciphers": "^2.0.0",
|
||||||
@@ -2153,6 +2153,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/better-auth/node_modules/@better-auth/core": {
|
||||||
|
"version": "1.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.4.10.tgz",
|
||||||
|
"integrity": "sha512-AThrfb6CpG80wqkanfrbN2/fGOYzhGladHFf3JhaWt/3/Vtf4h084T6PJLrDE7M/vCCGYvDI1DkvP3P1OB2HAg==",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"zod": "^4.1.12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@better-auth/utils": "0.3.0",
|
||||||
|
"@better-fetch/fetch": "1.1.21",
|
||||||
|
"better-call": "1.1.7",
|
||||||
|
"jose": "^6.1.0",
|
||||||
|
"kysely": "^0.28.5",
|
||||||
|
"nanostores": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/better-auth/node_modules/@better-auth/telemetry": {
|
||||||
|
"version": "1.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.4.10.tgz",
|
||||||
|
"integrity": "sha512-Dq4XJX6EKsUu0h3jpRagX739p/VMOTcnJYWRrLtDYkqtZFg+sFiFsSWVcfapZoWpRSUGYX9iKwl6nDHn6Ju2oQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@better-auth/utils": "0.3.0",
|
||||||
|
"@better-fetch/fetch": "1.1.21"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@better-auth/core": "1.4.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/better-call": {
|
"node_modules/better-call": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/better-call/-/better-call-1.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/better-call/-/better-call-1.1.7.tgz",
|
||||||
@@ -2382,9 +2412,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/drizzle-orm": {
|
"node_modules/drizzle-orm": {
|
||||||
"version": "0.44.7",
|
"version": "0.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.7.tgz",
|
"resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.1.tgz",
|
||||||
"integrity": "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ==",
|
"integrity": "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -2968,12 +2998,15 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/next/-/next-16.1.1.tgz",
|
||||||
|
"integrity": "sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "15.5.7",
|
"@next/env": "16.1.1",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
|
"baseline-browser-mapping": "^2.8.3",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.31",
|
||||||
"styled-jsx": "5.1.6"
|
"styled-jsx": "5.1.6"
|
||||||
@@ -2982,18 +3015,18 @@
|
|||||||
"next": "dist/bin/next"
|
"next": "dist/bin/next"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
"node": ">=20.9.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "15.5.7",
|
"@next/swc-darwin-arm64": "16.1.1",
|
||||||
"@next/swc-darwin-x64": "15.5.7",
|
"@next/swc-darwin-x64": "16.1.1",
|
||||||
"@next/swc-linux-arm64-gnu": "15.5.7",
|
"@next/swc-linux-arm64-gnu": "16.1.1",
|
||||||
"@next/swc-linux-arm64-musl": "15.5.7",
|
"@next/swc-linux-arm64-musl": "16.1.1",
|
||||||
"@next/swc-linux-x64-gnu": "15.5.7",
|
"@next/swc-linux-x64-gnu": "16.1.1",
|
||||||
"@next/swc-linux-x64-musl": "15.5.7",
|
"@next/swc-linux-x64-musl": "16.1.1",
|
||||||
"@next/swc-win32-arm64-msvc": "15.5.7",
|
"@next/swc-win32-arm64-msvc": "16.1.1",
|
||||||
"@next/swc-win32-x64-msvc": "15.5.7",
|
"@next/swc-win32-x64-msvc": "16.1.1",
|
||||||
"sharp": "^0.34.3"
|
"sharp": "^0.34.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@opentelemetry/api": "^1.1.0",
|
"@opentelemetry/api": "^1.1.0",
|
||||||
@@ -3020,6 +3053,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/next/node_modules/postcss": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
"version": "8.4.31",
|
"version": "8.4.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
|
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -3222,7 +3257,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "19.2.1",
|
"version": "19.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||||
|
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3230,14 +3267,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.2.1",
|
"version": "19.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
|
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^19.2.1"
|
"react": "^19.2.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
@@ -3398,7 +3437,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.1.17",
|
"version": "4.1.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||||
|
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,9 +27,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-auth": "^1.3.23",
|
"better-auth": "^1.3.23",
|
||||||
"dotenv": "^17.2.2",
|
"dotenv": "^17.2.2",
|
||||||
"drizzle-orm": "^0.44.5",
|
"drizzle-orm": "^0.45.0",
|
||||||
"net": "^1.0.2",
|
"net": "^1.0.2",
|
||||||
"next": "^15.5.2",
|
"next": "^16.0.0",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
"extends": [
|
"extends": [
|
||||||
"config:recommended"
|
"config:recommended"
|
||||||
],
|
],
|
||||||
|
"prConcurrentLimit": 1,
|
||||||
|
"prHourlyLimit": 1,
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
{
|
||||||
"matchUpdateTypes": [
|
"matchUpdateTypes": [
|
||||||
@@ -10,9 +12,10 @@
|
|||||||
"pin",
|
"pin",
|
||||||
"digest"
|
"digest"
|
||||||
],
|
],
|
||||||
|
"groupName": "all-dependencies",
|
||||||
"automerge": true,
|
"automerge": true,
|
||||||
"baseBranchPatterns": [
|
"matchPackageNames": [
|
||||||
"staging"
|
"*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2017",
|
"target": "ES2017",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -11,7 +15,7 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@@ -19,9 +23,19 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user