implement updates page
This commit is contained in:
parent
1bf731e738
commit
32924b5a1c
1119
package-lock.json
generated
1119
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -18,11 +18,13 @@
|
|||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "15.1.1",
|
||||
"node-fetch": "^3.3.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-markdown": "^9.0.3",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
|
|
|
@ -3,6 +3,7 @@ import Hero from '@/components/hero'
|
|||
import Features from '@/components/features'
|
||||
import Team from '@/components/team'
|
||||
import Footer from '@/components/footer'
|
||||
import Updates from '@/components/updates' // Import Blog component
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
|
@ -11,6 +12,7 @@ export default function Home() {
|
|||
<main>
|
||||
<Hero />
|
||||
<Features />
|
||||
<Updates />
|
||||
<Team />
|
||||
</main>
|
||||
<Footer />
|
||||
|
|
79
src/app/updates/[id]/page.jsx
Normal file
79
src/app/updates/[id]/page.jsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { updates } from '@/data/updates'
|
||||
import Link from 'next/link'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import DateDisplay from '@/components/DateDisplay'
|
||||
|
||||
export function generateStaticParams() {
|
||||
return updates.map((post) => ({
|
||||
id: post.id.toString(),
|
||||
}))
|
||||
}
|
||||
|
||||
export default function BlogPost({ params }) {
|
||||
const post = updates.find((p) => p.id.toString() === params.id)
|
||||
|
||||
if (!post) {
|
||||
return (
|
||||
<div className="container py-24">
|
||||
<h1 className="text-2xl font-bold">Post not found</h1>
|
||||
<Link href="/" className="text-blue-500 hover:underline">
|
||||
Back to home
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<article className="container py-24">
|
||||
<Link href="/" className="text-blue-500 hover:underline mb-8 block">
|
||||
← Back to home
|
||||
</Link>
|
||||
|
||||
<header className="mb-8">
|
||||
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
|
||||
|
||||
<div className="flex items-center gap-4 mb-6 p-4 border border-gray-800 rounded-lg">
|
||||
<img
|
||||
src={post.author.avatar}
|
||||
alt={post.author.name}
|
||||
className="w-16 h-16 rounded-full"
|
||||
width={64}
|
||||
height={64}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">{post.author.name}</h3>
|
||||
<p className="text-gray-400 text-sm mb-1">{post.author.role}</p>
|
||||
<p className="text-gray-400 text-sm">{post.author.bio}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DateDisplay
|
||||
timestamp={post.timestamp}
|
||||
className="text-gray-400"
|
||||
/>
|
||||
</header>
|
||||
|
||||
<div className="prose prose-invert max-w-none">
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
h1: ({children}) => <h1 className="text-3xl font-bold mt-8 mb-4">{children}</h1>,
|
||||
h2: ({children}) => <h2 className="text-2xl font-bold mt-6 mb-3">{children}</h2>,
|
||||
h3: ({children}) => <h3 className="text-xl font-bold mt-4 mb-2">{children}</h3>,
|
||||
p: ({children}) => <p className="mb-4">{children}</p>,
|
||||
ul: ({children}) => <ul className="list-disc ml-6 mb-4">{children}</ul>,
|
||||
ol: ({children}) => <ol className="list-decimal ml-6 mb-4">{children}</ol>,
|
||||
li: ({children}) => <li className="mb-1">{children}</li>,
|
||||
a: ({href, children}) => (
|
||||
<Link href={href} className="text-blue-500 hover:underline">
|
||||
{children}
|
||||
</Link>
|
||||
),
|
||||
strong: ({children}) => <strong className="font-bold">{children}</strong>,
|
||||
}}
|
||||
>
|
||||
{post.content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
52
src/app/updates/page.jsx
Normal file
52
src/app/updates/page.jsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { updates } from '@/data/updates'
|
||||
import Link from 'next/link'
|
||||
import DateDisplay from '@/components/DateDisplay'
|
||||
|
||||
export default function UpdatesPage() {
|
||||
const sortedUpdates = [...updates].sort((a, b) =>
|
||||
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="container py-24">
|
||||
<header className="mb-12">
|
||||
<h1 className="text-4xl font-bold mb-4">All Updates</h1>
|
||||
<Link href="/" className="text-blue-500 hover:underline">
|
||||
← Back to home
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
{sortedUpdates.map(post => (
|
||||
<div key={post.id} className="group p-6 border border-gray-800 rounded-lg hover:border-blue-500 transition-colors">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<img
|
||||
src={post.author.avatar}
|
||||
alt={post.author.name}
|
||||
className="w-10 h-10 rounded-full"
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
<div>
|
||||
<h4 className="text-sm font-medium">{post.author.name}</h4>
|
||||
<p className="text-xs text-gray-400">{post.author.role}</p>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-2xl font-semibold mb-2">{post.title}</h3>
|
||||
<DateDisplay
|
||||
timestamp={post.timestamp}
|
||||
className="text-sm text-gray-400 mb-3 block"
|
||||
/>
|
||||
<p className="text-gray-400 mb-4">{post.summary}</p>
|
||||
<Link
|
||||
href={`/updates/${post.id}`}
|
||||
className="text-blue-500 hover:underline inline-flex items-center"
|
||||
>
|
||||
Read more →
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
11
src/components/DateDisplay.jsx
Normal file
11
src/components/DateDisplay.jsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { format, parseISO } from 'date-fns';
|
||||
|
||||
export default function DateDisplay({ timestamp, className = "" }) {
|
||||
const date = parseISO(timestamp);
|
||||
|
||||
return (
|
||||
<time dateTime={timestamp} className={className} title={format(date, 'PPpp')}>
|
||||
{format(date, 'MMMM d, yyyy')}
|
||||
</time>
|
||||
);
|
||||
}
|
|
@ -20,6 +20,9 @@ export default function Header() {
|
|||
<Link href="#features" className="text-gray-300 hover:text-white">
|
||||
Features
|
||||
</Link>
|
||||
<Link href="#updates" className="text-gray-300 hover:text-white">
|
||||
Updates
|
||||
</Link>
|
||||
<Link href="#team" className="text-gray-300 hover:text-white">
|
||||
Team
|
||||
</Link>
|
||||
|
|
83
src/components/ui/table.jsx
Normal file
83
src/components/ui/table.jsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Table = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props} />
|
||||
</div>
|
||||
))
|
||||
Table.displayName = "Table"
|
||||
|
||||
const TableHeader = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
))
|
||||
TableHeader.displayName = "TableHeader"
|
||||
|
||||
const TableBody = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props} />
|
||||
))
|
||||
TableBody.displayName = "TableBody"
|
||||
|
||||
const TableFooter = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
||||
{...props} />
|
||||
))
|
||||
TableFooter.displayName = "TableFooter"
|
||||
|
||||
const TableRow = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
))
|
||||
TableRow.displayName = "TableRow"
|
||||
|
||||
const TableHead = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
))
|
||||
TableHead.displayName = "TableHead"
|
||||
|
||||
const TableCell = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props} />
|
||||
))
|
||||
TableCell.displayName = "TableCell"
|
||||
|
||||
const TableCaption = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props} />
|
||||
))
|
||||
TableCaption.displayName = "TableCaption"
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
56
src/components/updates.jsx
Normal file
56
src/components/updates.jsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import Link from 'next/link';
|
||||
import { updates } from '@/data/updates';
|
||||
import Image from 'next/image';
|
||||
import DateDisplay from './DateDisplay';
|
||||
|
||||
export default function Updates() {
|
||||
const recentUpdates = [...updates]
|
||||
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
||||
.slice(0, 2);
|
||||
|
||||
return (
|
||||
<section id="updates" className="container py-24">
|
||||
<div className="flex justify-between items-center mb-12">
|
||||
<h2 className="text-3xl font-bold">Latest Updates</h2>
|
||||
<Link
|
||||
href="/updates"
|
||||
className="text-blue-500 hover:underline inline-flex items-center"
|
||||
>
|
||||
View all updates →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
{recentUpdates.map(post => (
|
||||
<div key={post.id} className="group p-6 border border-gray-800 rounded-lg hover:border-blue-500 transition-colors">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<img
|
||||
src={post.author.avatar}
|
||||
alt={post.author.name}
|
||||
className="w-10 h-10 rounded-full"
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
<div>
|
||||
<h4 className="text-sm font-medium">{post.author.name}</h4>
|
||||
<p className="text-xs text-gray-400">{post.author.role}</p>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-2xl font-semibold mb-2">{post.title}</h3>
|
||||
<DateDisplay
|
||||
timestamp={post.timestamp}
|
||||
className="text-sm text-gray-400 mb-3 block"
|
||||
/>
|
||||
<p className="text-gray-400 mb-4">{post.summary}</p>
|
||||
<Link
|
||||
href={`/updates/${post.id}`}
|
||||
className="text-blue-500 hover:underline inline-flex items-center"
|
||||
>
|
||||
Read more →
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
65
src/data/authors.js
Normal file
65
src/data/authors.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
export const Authors = {
|
||||
CHRIS_CHROME: {
|
||||
id: 'chris_chrome',
|
||||
name: 'Chris Chrome',
|
||||
role: 'Administrator',
|
||||
ext: '1000',
|
||||
discord: '@chrischrome',
|
||||
avatar: '/chris.webp',
|
||||
bio: 'LiteNet Project Lead and Administrator'
|
||||
},
|
||||
CAYDEN: {
|
||||
id: 'cayden',
|
||||
name: 'Cayden',
|
||||
role: 'Administrator',
|
||||
ext: '1001',
|
||||
discord: '@freepbx',
|
||||
avatar: '/cayden.webp',
|
||||
bio: 'LiteNet Project Co-Lead and Administrator'
|
||||
},
|
||||
NICK: {
|
||||
id: 'nick',
|
||||
name: 'Nick',
|
||||
role: 'Administrator',
|
||||
ext: '1036',
|
||||
discord: '@gamewell',
|
||||
avatar: '/nick.webp',
|
||||
bio: 'Administrator at LiteNet'
|
||||
},
|
||||
FAUX_LEMONS: {
|
||||
id: 'faux_lemons',
|
||||
name: 'Faux Lemons',
|
||||
role: 'Administrator',
|
||||
ext: '1011',
|
||||
discord: '@faux_lemons',
|
||||
avatar: '/faux_lemons.webp',
|
||||
bio: 'Administrator at LiteNet'
|
||||
},
|
||||
ASHTON: {
|
||||
id: 'ashton',
|
||||
name: 'ashton',
|
||||
role: 'Administrator',
|
||||
ext: '1007',
|
||||
discord: '@ashtoncarlson',
|
||||
avatar: '/ashton.webp',
|
||||
bio: 'Administrator at LiteNet'
|
||||
},
|
||||
MADDIX: {
|
||||
id: 'maddix',
|
||||
name: 'Maddix',
|
||||
role: 'Moderator',
|
||||
ext: '1019',
|
||||
discord: '@maddix6859',
|
||||
avatar: '/maddix.webp',
|
||||
bio: 'Community Support and Moderation Team Member'
|
||||
},
|
||||
ROCORD: {
|
||||
id: 'rocord',
|
||||
name: 'rocord',
|
||||
role: 'Moderator',
|
||||
ext: '2222',
|
||||
discord: '@rocord',
|
||||
avatar: '/rocord.webp',
|
||||
bio: 'Website Developer and VoIP Specialist'
|
||||
}
|
||||
};
|
64
src/data/updates.js
Normal file
64
src/data/updates.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { Authors } from './authors';
|
||||
|
||||
export const updates = [
|
||||
{
|
||||
id: 2,
|
||||
title: "LiteNet & Snakecraft Hosting Partnership",
|
||||
timestamp: "2025-01-25T18:00:00Z",
|
||||
author: Authors.CHRIS_CHROME,
|
||||
summary: "Exciting announcement about our new partnership with Snakecraft Hosting",
|
||||
content: `
|
||||
We're excited to announce that **LiteNet** is now officially sponsored by [**Snakecraft Hosting**](https://go.litenet.tel/sch-affiliate)*! 🎉
|
||||
|
||||
As part of this partnership, they'll be providing us with a VPS to host LiteNet, and we're incredibly grateful for their support. Snakecraft Hosting has earned our trust with their **affordable pricing**, **wide range of services**, and a **solid reputation**—especially for a smaller hosting provider. We're proud to partner with them and help share what they have to offer with our community.
|
||||
|
||||
Don't worry—this won't change anything about LiteNet (aside from the server IP). Our commitment to providing a **safe, secure, and reliable platform** for all of you remains as strong as ever.
|
||||
|
||||
If you're curious about Snakecraft Hosting, feel free to check out [their website](https://go.litenet.tel/sch-affiliate)* or join their Discord server at [discord.gg/nZFQTaZWqT](https://discord.gg/nZFQTaZWqT). A big thank you to them for supporting LiteNet!
|
||||
|
||||
More details about the transfer will come within the next few days, so keep an eye out!
|
||||
|
||||
---
|
||||
|
||||
\**This is an affiliate link. If you use it to make a purchase, we'll receive a small commission in the form of credit for their services.*
|
||||
|
||||
**Note:**
|
||||
* We want to clarify that we haven't been paid to make this post, and all opinions expressed are our own. Snakecraft Hosting has not influenced our views in any way.
|
||||
* Any questions, comments, or concerns should be sent to the DMs of @ChrisChrome on Discord
|
||||
`
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: "Re: LiteNet/TandmX Service",
|
||||
timestamp: "2025-01-24T20:00:00Z",
|
||||
author: Authors.CHRIS_CHROME,
|
||||
summary: "Important announcement regarding the discontinuation of TandmX service on LiteNet",
|
||||
content: `
|
||||
We regret to inform everybody that as of tonight, January 24, 2025, LiteNet users will no longer be able to place or receive calls via TandmX.
|
||||
|
||||
## Decision Factors
|
||||
|
||||
This decision was not made lightly, and we understand that some users may not agree with the decision, but below is a short list of the major reasons behind the decision:
|
||||
|
||||
* Ever increasing restrictions as to what each independent group can do with their phone systems when they are connected to TandmX
|
||||
* Recent overreach and abuse of power pertaining to another groups phone system
|
||||
* An attempt to strong-arm the aforementioned group into removing their hosted line service over TandmX's offering
|
||||
|
||||
## Dialplan Changes
|
||||
|
||||
The discontinuation of TandmX service on LiteNet will also bring the following dialplan changes:
|
||||
|
||||
* TandmX will no longer be accessible
|
||||
* AstroCom will no longer require the "00" dial prefix and can be reached by simply dialing the number directly
|
||||
|
||||
## Moving Forward
|
||||
|
||||
We understand a few of our community members rely on LiteNet to get access to TandmX, and we regret that loss of connectivity. However, we look forward to working with our community to create more bridges to other communities, be it through AstroCom, or otherwise.
|
||||
|
||||
Regards,
|
||||
|
||||
**Chris Cookman**
|
||||
The LiteNet Group
|
||||
`
|
||||
}
|
||||
];
|
Loading…
Reference in a new issue