Initial commit
41
.gitignore
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
8
.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
8
.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/untitled1.iml" filepath="$PROJECT_DIR$/.idea/untitled1.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
12
.idea/untitled1.iml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
36
README.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
21
components.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": false,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.mjs",
|
||||
"css": "oldapp/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
14
eslint.config.mjs
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
});
|
||||
|
||||
const eslintConfig = [...compat.extends("next/core-web-vitals")];
|
||||
|
||||
export default eslintConfig;
|
7
jsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
5
next.config.mjs
Normal file
|
@ -0,0 +1,5 @@
|
|||
const nextConfig = {
|
||||
output: 'export',
|
||||
};
|
||||
|
||||
export default nextConfig;
|
BIN
oldapp/favicon.ico
Normal file
After Width: | Height: | Size: 25 KiB |
29
oldapp/layout.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
101
oldapp/page.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
||||
src/app/page.js
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
5620
package-lock.json
generated
Normal file
33
package.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "litenetwebsite",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-alert-dialog": "^1.1.4",
|
||||
"@radix-ui/react-avatar": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.4",
|
||||
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "15.1.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.1",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1"
|
||||
}
|
||||
}
|
8
postcss.config.mjs
Normal file
|
@ -0,0 +1,8 @@
|
|||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
BIN
public/ashton.webp
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
public/cayden.webp
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
public/chris.webp
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
public/faux_lemons.webp
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
public/litenet-logo.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
public/maddix.webp
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
public/nick.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/rocord.webp
Normal file
After Width: | Height: | Size: 2.4 KiB |
71
src/app/globals.css
Normal file
|
@ -0,0 +1,71 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 0%;
|
||||
--foreground: 0 0% 100%;
|
||||
--card: 0 0% 4%;
|
||||
--card-foreground: 0 0% 100%;
|
||||
--popover: 0 0% 4%;
|
||||
--popover-foreground: 0 0% 100%;
|
||||
--primary: 212 100% 47%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--secondary: 0 0% 13%;
|
||||
--secondary-foreground: 0 0% 100%;
|
||||
--muted: 0 0% 13%;
|
||||
--muted-foreground: 0 0% 63%;
|
||||
--accent: 0 0% 13%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
--destructive: 0 62% 30%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--border: 0 0% 13%;
|
||||
--input: 0 0% 13%;
|
||||
--ring: 212 100% 47%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset border-radius */
|
||||
@layer utilities {
|
||||
.rounded-lg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.rounded-md {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
.rounded-sm {
|
||||
border-radius: 0.125rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure proper centering */
|
||||
.container {
|
||||
@apply mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl;
|
||||
}
|
||||
|
22
src/app/layout.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { Inter } from 'next/font/google'
|
||||
import './globals.css'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata = {
|
||||
title: 'LiteNet - Free Community PBX',
|
||||
description: 'A free community PBX based on FreePBX. Get your own 4-digit number and start calling other members today!',
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
(<html lang="en" className="scroll-smooth">
|
||||
<body className={`${inter.className} min-h-screen bg-black text-gray-100`}>
|
||||
<div className="flex flex-col min-h-screen">
|
||||
{children}
|
||||
</div>
|
||||
</body>
|
||||
</html>)
|
||||
);
|
||||
}
|
||||
|
20
src/app/page.jsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import Header from '@/components/header'
|
||||
import Hero from '@/components/hero'
|
||||
import Features from '@/components/features'
|
||||
import Team from '@/components/team'
|
||||
import Footer from '@/components/footer'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main>
|
||||
<Hero />
|
||||
<Features />
|
||||
<Team />
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
65
src/components/features.jsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { Phone, Users, VoicemailIcon, Radio, MoreHorizontal, Network, Bot, UserCog } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
const features = [
|
||||
{
|
||||
name: 'Free Dial-in and Out',
|
||||
description: 'Call using +1 (610) LITENET / +1 (610) 548 3638',
|
||||
icon: Phone,
|
||||
},
|
||||
{
|
||||
name: 'Conference Rooms',
|
||||
description: 'Host or join conference calls with multiple participants',
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
name: 'Voicemail',
|
||||
description: 'Set up your own private voicemail and receive messages from anyone',
|
||||
icon: VoicemailIcon,
|
||||
},
|
||||
{
|
||||
name: 'Intercom and Paging',
|
||||
description: 'Easily communicate with other extensions and page groups',
|
||||
icon: Radio,
|
||||
},
|
||||
{
|
||||
name: 'IAX2 Trunking with AstroCom',
|
||||
description: <>Seamlessly integrate your FreePBX/Asterisk instance with <Link href="https://astrocom.tel/" className="text-sm underline underline-offset-4">AstroCom</Link>, officially supported by LiteNet</>,
|
||||
icon: Network,
|
||||
},
|
||||
{
|
||||
name: 'Discord Bot',
|
||||
description: 'Quickly create your own extension, manage page groups, and view Call Detail Records',
|
||||
icon: Bot,
|
||||
},
|
||||
{
|
||||
name: 'UCP Account',
|
||||
description: 'Log in to the User Control Panel to manage your account and settings',
|
||||
icon: UserCog,
|
||||
},
|
||||
{
|
||||
name: 'And More!',
|
||||
description: 'We\'re always adding new features to LiteNet!',
|
||||
icon: MoreHorizontal,
|
||||
},
|
||||
]
|
||||
|
||||
export default function Features() {
|
||||
return (
|
||||
(<section id="features" className="container py-24">
|
||||
<h2 className="mb-12 text-center text-3xl font-bold">Features</h2>
|
||||
<div className="grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{features.map((feature) => (
|
||||
<div key={feature.name} className="flex flex-col items-center text-center">
|
||||
<div className="mb-4 rounded-full bg-blue-500 p-3">
|
||||
<feature.icon className="h-6 w-6 text-gray-100" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-xl font-semibold">{feature.name}</h3>
|
||||
<p className="text-gray-400">{feature.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>)
|
||||
);
|
||||
}
|
||||
|
22
src/components/footer.jsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { TermsOfServiceModal } from './terms-of-service-modal'
|
||||
import { PrivacyPolicyModal } from './privacy-policy-modal'
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-gray-800 bg-black text-gray-300">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 flex flex-col items-center justify-between gap-4 py-10 md:h-24 md:flex-row md:py-0">
|
||||
<div className="flex flex-col items-center gap-4 px-8 md:flex-row md:gap-2 md:px-0">
|
||||
<img src="/litenet-logo.png" alt="LiteNet Logo" className="h-6 w-6" />
|
||||
<p className="text-center text-sm leading-loose md:text-left">
|
||||
© 2024 The LiteNet Group. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-4 items-center">
|
||||
<TermsOfServiceModal />
|
||||
<PrivacyPolicyModal />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
74
src/components/header.jsx
Normal file
|
@ -0,0 +1,74 @@
|
|||
import Link from 'next/link'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Phone } from 'lucide-react'
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
(<header
|
||||
className="sticky top-0 z-50 w-full border-b border-gray-800 bg-black/95 backdrop-blur supports-[backdrop-filter]:bg-black/60">
|
||||
<div className="container flex h-14 items-center">
|
||||
<div className="mr-4 hidden md:flex">
|
||||
<Link href="/" className="mr-6 flex items-center space-x-2">
|
||||
<img
|
||||
src="/litenet-logo.png"
|
||||
alt="LiteNet Logo"
|
||||
className="h-6 w-6" />
|
||||
<span className="hidden font-bold text-white sm:inline-block">LiteNet</span>
|
||||
</Link>
|
||||
<nav className="flex items-center space-x-6 text-sm font-medium">
|
||||
<Link href="#features" className="text-gray-300 hover:text-white">
|
||||
Features
|
||||
</Link>
|
||||
<Link href="#team" className="text-gray-300 hover:text-white">
|
||||
Team
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-1 items-center justify-between space-x-2 md:justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-gray-700 bg-gray-900 text-white hover:bg-gray-800"
|
||||
asChild>
|
||||
<a
|
||||
href="https://discord.litenet.tel"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="mr-2 h-4 w-4">
|
||||
<circle cx="9" cy="12" r="1" />
|
||||
<circle cx="15" cy="12" r="1" />
|
||||
<path d="M7.5 7.5c3.5-1 5.5-1 9 0" />
|
||||
<path d="M7 16.5c3.5 1 6.5 1 10 0" />
|
||||
<path
|
||||
d="M15.5 17c0 1 1.5 3 2 3 1.5 0 2.833-1.667 3.5-3 .667-1.667.5-5.833-1.5-11.5-1.457-1.015-3-1.34-4.5-1.5l-1 2.5" />
|
||||
<path
|
||||
d="M8.5 17c0 1-1.356 3-1.832 3-1.429 0-2.698-1.667-3.333-3-.635-1.667-.476-5.833 1.428-11.5C6.151 4.485 7.545 4.16 9 4l1 2.5" />
|
||||
</svg>
|
||||
Join Discord
|
||||
</a>
|
||||
</Button>
|
||||
<Button className="bg-blue-600 text-white hover:bg-blue-700" asChild>
|
||||
<a
|
||||
href="https://pbx.phreaki.ng/ucp/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<Phone className="mr-2 h-4 w-4" />
|
||||
UCP Login
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>)
|
||||
);
|
||||
}
|
||||
|
63
src/components/hero.jsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Phone } from 'lucide-react'
|
||||
|
||||
export default function Hero() {
|
||||
return (
|
||||
(<section
|
||||
className="container flex flex-col items-center justify-center space-y-4 py-24 text-center">
|
||||
<img
|
||||
src="/litenet-logo.png"
|
||||
alt="LiteNet Logo"
|
||||
className="h-24 w-24" />
|
||||
<h1
|
||||
className="text-4xl font-extrabold tracking-tight sm:text-5xl md:text-6xl">Welcome to LiteNet</h1>
|
||||
<p
|
||||
className="max-w-[42rem] leading-normal text-gray-400 sm:text-xl sm:leading-8">
|
||||
A free community PBX based on FreePBX. Get your own 4-digit extension and start calling other members today!
|
||||
</p>
|
||||
<div className="flex justify-center space-x-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-gray-700 bg-gray-900 text-white hover:bg-gray-800"
|
||||
asChild>
|
||||
<a
|
||||
href="https://discord.litenet.tel"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="mr-2 h-4 w-4">
|
||||
<circle cx="9" cy="12" r="1" />
|
||||
<circle cx="15" cy="12" r="1" />
|
||||
<path d="M7.5 7.5c3.5-1 5.5-1 9 0" />
|
||||
<path d="M7 16.5c3.5 1 6.5 1 10 0" />
|
||||
<path
|
||||
d="M15.5 17c0 1 1.5 3 2 3 1.5 0 2.833-1.667 3.5-3 .667-1.667.5-5.833-1.5-11.5-1.457-1.015-3-1.34-4.5-1.5l-1 2.5" />
|
||||
<path
|
||||
d="M8.5 17c0 1-1.356 3-1.832 3-1.429 0-2.698-1.667-3.333-3-.635-1.667-.476-5.833 1.428-11.5C6.151 4.485 7.545 4.16 9 4l1 2.5" />
|
||||
</svg>
|
||||
Join Discord
|
||||
</a>
|
||||
</Button>
|
||||
<Button className="bg-blue-600 text-white hover:bg-blue-700" asChild>
|
||||
<a
|
||||
href="https://pbx.phreaki.ng/ucp/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<Phone className="mr-2 h-4 w-4" />
|
||||
UCP Login
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</section>)
|
||||
);
|
||||
}
|
||||
|
74
src/components/privacy-policy-modal.jsx
Normal file
|
@ -0,0 +1,74 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
|
||||
export function PrivacyPolicyModal() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<button className="text-sm underline underline-offset-4">Privacy Policy</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[625px] bg-black text-white border border-gray-800">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl font-bold">Privacy Policy</DialogTitle>
|
||||
<DialogDescription className="text-gray-400">
|
||||
LiteNet is committed to protecting your privacy. This Privacy Policy explains our practices regarding the collection, use, and disclosure of your information.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="h-[400px] w-full rounded-md border border-gray-800 p-4">
|
||||
<h2 className="text-lg font-semibold mb-2">1. Information Collection and Use</h2>
|
||||
<p className="mb-4">
|
||||
Privacy is a primary goal of LiteNet. We collect and maintain only necessary information to ensure the safety and functionality of our system.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">2. Call Detail Records (CDR) Logs</h2>
|
||||
<p className="mb-4">
|
||||
CDR Logs are accessible only by authorized personnel. These logs are used solely for system debugging purposes and to identify and block malicious callers. Access to these logs is strictly controlled and monitored.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">3. Call Recording</h2>
|
||||
<p className="mb-4">
|
||||
By default, call recording is disabled for all users. However, at the request of a user, call recording can be enabled for their individual extension. Recorded calls are accessible only by the owner of the extension.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">4. Voicemail Privacy</h2>
|
||||
<p className="mb-4">
|
||||
LiteNet does not track or monitor voicemails. Users can expect an inherent level of privacy when using the voicemail system. Voicemail messages are accessible only by the owner of the extension.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">5. Data Deletion</h2>
|
||||
<p className="mb-4">
|
||||
Users have the right to request the deletion of their data at any time. This can be done by using the "Delete your extension" function within the user interface. Upon deletion, all relevant user information, including but not limited to login credentials, voicemail messages, and voicemail PIN numbers, will be immediately and permanently removed from our system.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">6. Data Security</h2>
|
||||
<p className="mb-4">
|
||||
We implement a variety of security measures to maintain the safety of your personal information. However, no method of transmission over the Internet or method of electronic storage is 100% secure.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">7. Changes to This Privacy Policy</h2>
|
||||
<p className="mb-4">
|
||||
We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "last updated" date.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">8. Contact Us</h2>
|
||||
<p className="mb-4">
|
||||
If you have any questions or concerns regarding this Privacy Policy, please contact our staff members. They are available to assist you with any privacy-related inquiries.
|
||||
</p>
|
||||
|
||||
<p className="mt-4 font-semibold">
|
||||
By using LiteNet's services, you acknowledge that you have read and understood this Privacy Policy and agree to its terms.
|
||||
</p>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
62
src/components/team.jsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
|
||||
const adminTeam = [
|
||||
{ name: 'Chris Chrome', ext: '1000', image: '/chris.webp', discord: '@chrischrome' },
|
||||
{ name: 'Cayden', ext: '1001', image: '/cayden.webp', discord: '@freepbx' },
|
||||
{ name: 'Nick', ext: '1036', image: '/nick.webp', discord: '@gamewell' },
|
||||
{ name: 'Faux_Lemons', ext: '1011', image: '/faux_lemons.webp', discord: '@faux_lemons' },
|
||||
]
|
||||
|
||||
const modTeam = [
|
||||
{ name: 'ashton', ext: '1007', image: '/ashton.webp', discord: '@ashtoncarlson' },
|
||||
{ name: 'Maddix', ext: '1019', image: '/maddix.webp', discord: '@maddix6859' },
|
||||
{ name: 'rocord', ext: '2222', image: '/rocord.webp', discord: '@rocord01' },
|
||||
]
|
||||
|
||||
function TeamMember({ name, ext, image, discord }) {
|
||||
return (
|
||||
(<Card className="bg-gray-950 text-white">
|
||||
<CardHeader className="flex flex-row items-center gap-4 space-y-0">
|
||||
<Avatar className="h-14 w-14">
|
||||
<AvatarImage src={image} alt={name} />
|
||||
<AvatarFallback>{name[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<CardTitle>{name}</CardTitle>
|
||||
<div className="text-sm text-gray-400">{discord}</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-sm text-gray-300">x{ext}</div>
|
||||
</CardContent>
|
||||
</Card>)
|
||||
);
|
||||
}
|
||||
|
||||
export default function Team() {
|
||||
return (
|
||||
(<section id="team" className="container py-24">
|
||||
<h2 className="mb-12 text-center text-3xl font-bold text-white">Our Team</h2>
|
||||
<div className="space-y-12">
|
||||
<div>
|
||||
<h3 className="mb-4 text-2xl font-semibold text-gray-200">Administration Team</h3>
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{adminTeam.map((member) => (
|
||||
<TeamMember key={member.ext} {...member} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="mb-4 text-2xl font-semibold text-gray-200">Moderation Team</h3>
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{modTeam.map((member) => (
|
||||
<TeamMember key={member.ext} {...member} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>)
|
||||
);
|
||||
}
|
||||
|
92
src/components/terms-of-service-modal.jsx
Normal file
|
@ -0,0 +1,92 @@
|
|||
"use client";
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
|
||||
export function TermsOfServiceModal() {
|
||||
React.useEffect(() => {
|
||||
if (window.location.hash === '#tos') {
|
||||
document.getElementById('tos-trigger').click();
|
||||
}
|
||||
}, []);
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<button id="tos-trigger" className="text-sm underline underline-offset-4">Terms of Service</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[625px] bg-black text-white border border-gray-800">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl font-bold">Terms of Service</DialogTitle>
|
||||
<DialogDescription className="text-gray-400">
|
||||
Please read our Terms of Service carefully.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="h-[400px] w-full rounded-md border border-gray-800 p-4">
|
||||
<h2 className="text-lg font-semibold mb-2">1. Acceptance of Terms</h2>
|
||||
<p className="mb-4">
|
||||
By accessing or using LiteNet's services, you agree to comply with and be bound by these Terms of Service. If you do not agree to these terms, please do not use our services.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">2. Discord Community Rules</h2>
|
||||
<ul className="list-disc pl-5 mb-4">
|
||||
<li>External drama is strictly prohibited.</li>
|
||||
<li>Discrimination of any kind, including but not limited to sexism, racism, homophobia, and transphobia, is not tolerated.</li>
|
||||
<li>Users must use common sense in all channels. Spamming and posting NSFW content are prohibited.</li>
|
||||
<li>Paging should only occur during normal hours, not in the middle of the night.</li>
|
||||
<li>Staff reserve the right to delete messages, warn, mute, kick, and ban users for any reason, within reason.</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">3. Fax Usage Rules</h2>
|
||||
<ul className="list-disc pl-5 mb-4">
|
||||
<li>NSFW content is prohibited in faxes.</li>
|
||||
<li>Faxes that would take an excessive amount of time to send are not allowed.</li>
|
||||
<li>Extremely long faxes are prohibited.</li>
|
||||
<li>Sending faxes that waste ink excessively is not permitted.</li>
|
||||
<li>Users should not send faxes they wouldn't want to receive themselves.</li>
|
||||
</ul>
|
||||
<p className="mb-4">
|
||||
Note: These fax rules can be waived with prior consent from the recipient.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">4. Conduct</h2>
|
||||
<p className="mb-4">
|
||||
Users are expected to behave in a respectful and lawful manner. The absence of a specific written rule does not imply that an action is permitted. LiteNet reserves the right to determine what constitutes appropriate behavior.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">5. Modifications to Terms</h2>
|
||||
<p className="mb-4">
|
||||
These Terms of Service are subject to change. LiteNet will make an announcement whenever changes occur. It is the user's responsibility to review these terms periodically.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">6. Termination of Service</h2>
|
||||
<p className="mb-4">
|
||||
LiteNet reserves the right to terminate or suspend access to our services, without prior notice or liability, for any reason whatsoever, including without limitation if you breach the Terms of Service.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">7. Limitation of Liability</h2>
|
||||
<p className="mb-4">
|
||||
LiteNet shall not be liable for any indirect, incidental, special, consequential or punitive damages, or any loss of profits or revenues, whether incurred directly or indirectly, or any loss of data, use, goodwill, or other intangible losses, resulting from your access to or use of or inability to access or use the services.
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-2">8. Governing Law</h2>
|
||||
<p className="mb-4">
|
||||
These Terms shall be governed and construed in accordance with the laws of the jurisdiction in which LiteNet operates, without regard to its conflict of law provisions.
|
||||
</p>
|
||||
|
||||
<p className="mt-4 font-semibold">
|
||||
By using LiteNet's services, you acknowledge that you have read, understood, and agree to be bound by these Terms of Service.
|
||||
</p>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
33
src/components/ui/avatar.jsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Avatar = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
|
||||
{...props} />
|
||||
))
|
||||
Avatar.displayName = AvatarPrimitive.Root.displayName
|
||||
|
||||
const AvatarImage = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
{...props} />
|
||||
))
|
||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
||||
|
||||
const AvatarFallback = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
))
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
47
src/components/ui/button.jsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
(<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props} />)
|
||||
);
|
||||
})
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
50
src/components/ui/card.jsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("rounded-lg border bg-black text-card-foreground shadow-sm", className)}
|
||||
{...props} />
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props} />
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
|
||||
{...props} />
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props} />
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props} />
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
94
src/components/ui/dialog.jsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Dialog = DialogPrimitive.Root
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal
|
||||
|
||||
const DialogClose = DialogPrimitive.Close
|
||||
|
||||
const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
))
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
|
||||
const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
<DialogPrimitive.Close
|
||||
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<div
|
||||
className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)}
|
||||
{...props} />
|
||||
)
|
||||
DialogHeader.displayName = "DialogHeader"
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<div
|
||||
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
||||
{...props} />
|
||||
)
|
||||
DialogFooter.displayName = "DialogFooter"
|
||||
|
||||
const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||
{...props} />
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
|
||||
const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props} />
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
}
|
38
src/components/ui/scroll-area.jsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
import * as React from "react"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
))
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||
|
||||
const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
))
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
||||
|
||||
export { ScrollArea, ScrollBar }
|
6
src/lib/utils.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
61
tailwind.config.mjs
Normal file
|
@ -0,0 +1,61 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))'
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))'
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))'
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))'
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))'
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))'
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))'
|
||||
},
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
chart: {
|
||||
'1': 'hsl(var(--chart-1))',
|
||||
'2': 'hsl(var(--chart-2))',
|
||||
'3': 'hsl(var(--chart-3))',
|
||||
'4': 'hsl(var(--chart-4))',
|
||||
'5': 'hsl(var(--chart-5))'
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
};
|