Skip to content

Commit

Permalink
Merge branch 'main' into feature/animated-headline
Browse files Browse the repository at this point in the history
  • Loading branch information
amir-abxn committed Jan 15, 2025
2 parents 3075324 + 01826cc commit 92fe465
Show file tree
Hide file tree
Showing 53 changed files with 1,493 additions and 171 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ CRYPTOMUS_ID="id"
CRYPTOMUS_API_KEY="key"

CARDLINK_API_KEY="key"

ADMITAD_POSTBACK_KEY="key"
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
cp .env.example .env
docker-compose up -d
pnpm install
pnpm run prisma:push
npx prisma db push

pnpm run dev
```
Expand All @@ -17,9 +17,8 @@ To automatically format code, it is important to properly configure your IDE. Se

### Stack

- [Next.js](https://nextjs.org/docs/getting-started/project-structure#app-routing-conventions) app Routing Conventions
- [Next.js](https://nextjs.org/docs/getting-started/project-structure#app-routing-conventions) App Routing Conventions
- [Prisma ORM](https://www.prisma.io/docs/orm/overview/introduction/what-is-prisma)
- [FSD](https://feature-sliced.design/ru/docs/get-started/overview) folder structure

### DB

Expand Down
33 changes: 33 additions & 0 deletions app/[locale]/[...not-found]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { PageSection } from "@/components/page-section"
import { getScopedI18n } from "@/shared/locales/server"

export default async function NotFound() {
const t = await getScopedI18n("not_found")

return (
<PageSection className="flex flex-col items-center">
<h1 className="text-4xl font-bold mb-8 text-center">{t("404")}</h1>
<h3 className="text-xl mb-8 text-center">{t("404_sub")}</h3>
<form
action="https://www.google.com/search"
method="get"
className="flex flex-col items-center"
>
<input
type="text"
name="q"
placeholder={t("search_site")}
required
className="border p-3 mb-4 w-80 rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<input type="hidden" name="sitesearch" value="frkn.org" />
<button
type="submit"
className="bg-blue-600 hover:bg-blue-500 text-white px-5 py-3 rounded"
>
{t("search")}
</button>
</form>
</PageSection>
)
}
2 changes: 1 addition & 1 deletion app/[locale]/auth/components/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function Form() {
const result = await login({ password: sha3_512(mnemonic) })
if (result.status === "success") {
analytics("auth")
window.location.href = "/connect"
window.location.href = "/dashboard/connections"
} else {
toast.error(result.message)
}
Expand Down
14 changes: 14 additions & 0 deletions app/[locale]/dashboard/connections/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Skeleton } from "@/components/ui/skeleton"

export default function Loading() {
return (
<div>
<div className="grid grid-rows-1 lg:grid-cols-[1fr_auto] max-w-6xl w-full mx-auto px-4 gap-6">
<div className="flex flex-col lg:flex-row items-center justify-center gap-8 p-8">
<Skeleton className="w-full h-16 mt-2" />
<Skeleton className="w-full h-16 mt-2" />
</div>
</div>
</div>
)
}
291 changes: 291 additions & 0 deletions app/[locale]/dashboard/connections/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
"use client"
import { QrModal } from "@/components/qr-modal"
import { SubLinkInput } from "@/components/sub-link-input"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { formatBytes } from "@/shared/format/bytes"
import { formatExpire, formatStrategy } from "@/shared/format/strategy"
import { useCurrentLocale, useScopedI18n } from "@/shared/locales/client"
import { trpc } from "@/shared/trpc"
import { Copy, LayoutGrid, Loader2, QrCode } from "lucide-react"
import Link from "next/link"
import { useState } from "react"
import {
AiFillAndroid,
AiFillApple,
AiFillWindows,
AiOutlineAndroid,
AiOutlineApple,
AiOutlineLinux,
AiOutlineMacCommand,
AiOutlineWindows,
} from "react-icons/ai"

export function Main() {
const t = useScopedI18n("app.dashboard")
const locale = useCurrentLocale()
const [isModalOpen, setModalOpen] = useState(false)
const [qr, setQr] = useState("")
const showQr = (data: string) => {
setQr(data)
setModalOpen(true)
}
const { data, isLoading, isSuccess, refetch } = trpc.xray.get.useQuery(
undefined,
{
refetchOnWindowFocus: false,
},
)

if (isLoading) {
return (
<div className="flex flex-col lg:flex-row items-center justify-center gap-8 p-8">
<Loader2 className="ml-4 h-6 w-6 animate-spin" />
</div>
)
}

if (!data || !isSuccess) {
return (
<div className="text-center mt-4">
<p>{t("error_message")}</p>
<div className="flex justify-center mt-2">
<button
onClick={() => refetch()}
disabled={isLoading}
className="px-4 py-2 bg-blue-500 text-white rounded flex items-center justify-center"
>
{isLoading && <Loader2 className="mr-2 h-6 w-6 animate-spin" />}
{t("reload_button")}
</button>
</div>
</div>
)
}

return (
<Card>
<CardHeader>
<CardTitle className="text-2xl/8 font-semibold text-foreground sm:text-xl/8">
{t("title")}
</CardTitle>
<CardDescription>
{t("status")}: {data.status}
<br />
{t("traffic_limit")}: {formatBytes(data.limit)}{" "}
{formatStrategy(data.limit_reset_strategy, locale)}
{formatExpire(data.expire, locale)}
<br />
{t("used_traffic")}: {formatBytes(data.used_traffic)}
</CardDescription>
</CardHeader>

<CardContent className="space-y-8">
<div>
<CardTitle className="mb-2">XRay</CardTitle>
<CardDescription className="text-balance mb-2">
{t("xrayDescription")}
</CardDescription>
<div className="flex flex-wrap gap-2 mb-2">
<Button size="sm" asChild>
<Link
href="https://apps.microsoft.com/detail/9pdfnl3qv2s5"
target="_blank"
>
<AiFillWindows className="mr-1" />
Windows
</Link>
</Button>
<Button size="sm" asChild>
<Link
href="https://apps.apple.com/ru/app/foxray/id6448898396"
target="_blank"
>
<AiFillApple className="mr-1" />
iOS & MacOS
</Link>
</Button>
<Button size="sm" asChild>
<Link
href="https://play.google.com/store/apps/details?id=app.hiddify.com"
target="_blank"
>
<AiFillAndroid className="mr-1" />
Android
</Link>
</Button>
</div>

<Table>
<TableBody>
<TableRow className="bg-muted/50">
<TableCell className="font-medium">
<SubLinkInput value={data.subscription_url} />
</TableCell>
<TableCell>
<div className="flex gap-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
aria-label="Copy Config"
size="sm"
onClick={() => {
navigator.clipboard.writeText(data.subscription_url)
}}
>
<Copy className="h-4 w-4 md:mr-2" />
<span className="hidden md:block">{t("copy")}</span>
</Button>
</TooltipTrigger>
<TooltipContent className="max-w-56 break-words drop-shadow-md">
{data.subscription_url}
</TooltipContent>
</Tooltip>
<Button
aria-label="Show QR Code"
onClick={() => showQr(data.subscription_url)}
size="sm"
>
<QrCode className="h-4 w-4 md:mr-2" />
<span className="hidden md:block">{t("showQr")}</span>
</Button>
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<div>
<CardTitle className="mb-2">Shadowsocks</CardTitle>
<CardDescription className="text-balance mb-2">
{t("shadowsocksDescription")}
</CardDescription>
<div className="flex flex-wrap gap-2 mb-2">
<Button size="sm" asChild>
<Link
href="https://s3.amazonaws.com/outline-releases/client/windows/stable/Outline-Client.exe"
target="_blank"
>
<AiOutlineWindows className="mr-1" />
Windows
</Link>
</Button>
<Button size="sm" asChild>
<Link
href="https://itunes.apple.com/us/app/outline-app/id1356178125"
target="_blank"
>
<AiOutlineMacCommand className="mr-1" />
MacOS
</Link>
</Button>
<Button size="sm" asChild>
<Link
href="https://s3.amazonaws.com/outline-releases/client/linux/stable/Outline-Client.AppImage"
target="_blank"
>
<AiOutlineLinux className="mr-1" />
Linux
</Link>
</Button>
<Button size="sm" asChild>
<Link
href="https://itunes.apple.com/us/app/outline-app/id1356177741"
target="_blank"
>
<AiOutlineApple className="mr-1" />
iOS
</Link>
</Button>
<Button size="sm" asChild>
<Link
href="https://play.google.com/store/apps/details?id=org.outline.android.client"
target="_blank"
>
<AiOutlineAndroid className="mr-1" />
Android
</Link>
</Button>
</div>

<Table>
<TableHeader>
<TableRow>
<TableHead>{t("country")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.ss_links.map(({ link, country }) => (
<TableRow key={link}>
<TableCell className="font-medium">{country}</TableCell>
<TableCell>
<div className="flex gap-2">
<Button size="sm" asChild>
<Link href={link}>
<LayoutGrid className="h-4 w-4 md:mr-2" />
<span className="hidden md:block">
{t("openInApp")}
</span>
</Link>
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
aria-label="Copy Config"
size="sm"
onClick={() => {
navigator.clipboard.writeText(link)
}}
>
<Copy className="h-4 w-4 md:mr-2" />
<span className="hidden md:block">{t("copy")}</span>
</Button>
</TooltipTrigger>
<TooltipContent className="max-w-56 break-words drop-shadow-md">
{link}
</TooltipContent>
</Tooltip>
<Button
aria-label="Show QR Code"
onClick={() => showQr(link)}
size="sm"
>
<QrCode className="h-4 w-4 md:mr-2" />
<span className="hidden md:block">{t("showQr")}</span>
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>

<QrModal
isOpen={isModalOpen}
close={() => setModalOpen(false)}
data={qr}
/>
</Card>
)
}
5 changes: 5 additions & 0 deletions app/[locale]/dashboard/connections/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Main } from "./main"

export default function Page() {
return <Main />
}
Loading

0 comments on commit 92fe465

Please sign in to comment.