Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
# TinaCMS — Copy this file to .env and fill in your values.
# Values are generated at app.tina.io after connecting your GitHub repo.
#
# IMPORTANT: Tina reads .env (not .env.local) during tinacms build.
# For local development you can put values in .env.local too, but
# for Vercel add them directly in the project's Environment Variables.

NEXT_PUBLIC_TINA_CLIENT_ID=
TINA_TOKEN=
# Canonical site URL (metadataBase, Open Graph absolute URLs, sitemap). Set in production / Vercel.
# Example: https://www.worldfishdigital.org
# NEXT_PUBLIC_SITE_URL=https://www.example.org

# Automatically set by Vercel — no action needed:
# NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF=
3 changes: 0 additions & 3 deletions .eslintrc.json

This file was deleted.

19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: CI

on:
push:
branches: [main, master]
pull_request:

jobs:
lint-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run build
5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
package-lock.json
yarn.lock

# dependencies
Expand Down Expand Up @@ -36,10 +35,6 @@ yarn-error.log*
# vercel
.vercel

# TinaCMS — generated at build time, do not commit
/public/admin
/tina/__generated__

#vscode settings
.vscode

Expand Down
144 changes: 18 additions & 126 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,140 +1,32 @@
# Peskas.show
# WorldFish Digital

Official website for Peskas™ - An open-source digital platform for small-scale fisheries.
Main site for [WorldFish](https://www.worldfishcenter.org/) digital tools and platforms—including **Peskas™** and related products—news, and resources.

## Tech Stack
## Run locally

- **Framework**: [Next.js 16.1.1](https://nextjs.org/) with App Router
- **React**: 19.0.0
- **Styling**: SCSS/CSS with Bootstrap-based theme
- **Content**: Markdown-based blog system
- **Deployment**: Vercel
You need **Node.js 20+** (see `.nvmrc`).

## Getting Started

### Prerequisites

- Node.js >= 20.9.0 (see `.nvmrc`)
- npm or yarn

### Installation

1. Clone the repository
2. Install dependencies:
```bash
npm install
```

3. Copy environment variables:
```bash
cp .env.example .env.local
```
Edit `.env.local` with your configuration if needed.

4. Run the development server:
```bash
npm run dev
```

5. Open [http://localhost:3000](http://localhost:3000) in your browser.

## Project Structure

```
peskas.show/
├── app/ # Next.js App Router pages
│ ├── blog/ # Blog listing and individual posts
│ ├── data-resources/ # Data resources page
│ ├── how-it-works/ # How it works page
│ ├── layout.js # Root layout
│ ├── page.js # Homepage
│ ├── sitemap.js # Dynamic sitemap generation
│ └── robots.js # Robots.txt generation
├── components/ # React components
│ ├── elements/ # Reusable UI elements
│ ├── layout/ # Layout components (Header, Footer, etc.)
│ └── sections/ # Page sections
├── lib/ # Utility functions
│ ├── posts.js # Blog post utilities
│ └── constants.js # Application constants
├── posts/ # Markdown blog posts
├── public/ # Static assets
└── styles/ # Global styles
```bash
npm install
cp .env.example .env.local # optional; set your public site URL for previews
npm run dev
```

## Adding Blog Posts

1. Create a new `.md` file in the `posts/` directory
2. Add frontmatter with required fields:
```yaml
---
title: "Your Post Title"
author: "Author Name"
date: "2024-01-01"
draft: false
description: "Post description"
tags:
- tag1
- tag2
coverImage: "/assets/imgs/page/blog/image.jpg"
---
```
3. Write your content below the frontmatter
4. Posts with `draft: true` will not appear in production

See `docs/BLOG_SETUP.md` for more details.

## Available Scripts
Open [http://localhost:3000](http://localhost:3000).

- `npm run dev` - Start development server
- `npm run build` - Build for production
- `npm run start` - Start production server
- `npm run lint` - Run ESLint
- `npm run sass` - Watch and compile SCSS (if needed)
## Blog posts

## Deployment
Add Markdown files under `posts/`. Use frontmatter for title, date, `draft` (hide when `true`), and optionally `channel` (`worldfish` or `peskas`). Images and assets live under `public/`.

This project is configured for deployment on Vercel. See `docs/VERCEL_SETUP.md` for deployment details.
## Production build

### Environment Variables

Configure the following in your deployment platform:

- `NODE_ENV` - Set to `production` for production builds
- `NEXT_PUBLIC_SITE_URL` - Full URL of your site (e.g., `https://peskas.show`)

See `.env.example` for all available environment variables.

## Features

- ✅ Modern Next.js App Router architecture
- ✅ Server-side rendering (SSR) and static site generation (SSG)
- ✅ Markdown-based blog system
- ✅ SEO optimized (sitemap, robots.txt, metadata)
- ✅ Error boundaries for graceful error handling
- ✅ Responsive design
- ✅ Performance optimized

## Development

### Code Style

- ESLint is configured with Next.js recommended rules
- Prettier is configured for consistent code formatting
- Use 4-space indentation (as per Prettier config)

### Key Features

- **Error Boundaries**: Graceful error handling in production
- **Constants**: Centralized configuration in `lib/constants.js`
- **Type Safety**: JSDoc comments for better IDE support (TypeScript migration optional)

## Learn More
```bash
npm run build
npm start
```

- [Next.js Documentation](https://nextjs.org/docs)
- [React Documentation](https://react.dev)
- [Next.js App Router](https://nextjs.org/docs/app)
Deploy however you host Node apps; many teams use **Vercel**. Set **`NEXT_PUBLIC_SITE_URL`** to your live site URL so links and social previews resolve correctly.

## License

See LICENSE file for details.
See `LICENSE`.
14 changes: 10 additions & 4 deletions app/blog/[slug]/page.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import BlogPostClient from "@/components/sections/BlogPostClient";
import { getPostBySlug, getAllPostSlugs } from "@/lib/posts";
import { notFound } from "next/navigation";
import { POST_CHANNELS, WORLD_FISH_SITE, PESKAS_PRODUCT } from "@/lib/constants";

export async function generateMetadata({ params }) {
const { slug } = await params;
const post = getPostBySlug(slug);
if (!post) return { title: 'Post Not Found' };
if (!post || post.draft) return { title: 'Post Not Found' };

const titleSuffix =
post.channel === POST_CHANNELS.peskas
? `${PESKAS_PRODUCT.name} — ${WORLD_FISH_SITE.name}`
: WORLD_FISH_SITE.name;

return {
title: `${post.title} - Peskas Blog`,
title: `${post.title} | ${titleSuffix}`,
description: post.description || post.content?.substring(0, 160),
authors: post.author ? [{ name: post.author }] : [],
robots: {
Expand All @@ -30,8 +36,8 @@ export async function generateStaticParams() {
export default async function BlogPost({ params }) {
const { slug } = await params;
const post = getPostBySlug(slug);
if (!post) {

if (!post || post.draft) {
notFound();
}

Expand Down
53 changes: 23 additions & 30 deletions app/blog/page.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import Link from "next/link";
import Layout from "@/components/layout/Layout";
import { getAllPosts } from "@/lib/posts";
import { BLOG_CONFIG } from "@/lib/constants";
import Link from 'next/link';
import Layout from '@/components/layout/Layout';
import BlogCoverImage from '@/components/content/BlogCoverImage';
import { getAllPosts } from '@/lib/posts';
import { BLOG_WORLD_FISH, WORLD_FISH_SITE } from '@/lib/constants';

export const metadata = {
title: `${BLOG_CONFIG.title} - ${BLOG_CONFIG.description}`,
description: BLOG_CONFIG.description,
title: `${BLOG_WORLD_FISH.title} | ${WORLD_FISH_SITE.name}`,
description: BLOG_WORLD_FISH.description,
robots: {
index: true,
follow: true,
Expand All @@ -18,30 +19,27 @@ export const metadata = {

export default async function Blog() {
const posts = getAllPosts();

return (
<Layout>
<section className="section-box">
<div className="banner-hero banner-breadcrums bg-gray-100">
<div className="container text-center">
<div className="row">
<div className="col-lg-12">
<h1 className="text-display-3 color-gray-900 mb-20">{BLOG_CONFIG.title}</h1>
<p className="text-heading-6 color-gray-600 mb-20">
{BLOG_CONFIG.description}
</p>
</div>
<section className="section-box wfSectionDark wfPadHeroSm">
<div className="container text-center">
<div className="row">
<div className="col-lg-10 mx-auto">
<h1 className="display-3 wfTitleHeroTight wfTitleHeroMb">{BLOG_WORLD_FISH.title}</h1>
<p className="wfLeadMdStatic">{BLOG_WORLD_FISH.description}</p>
</div>
</div>
</div>
</section>
<section className="section-box">
<div className="container mt-90">
<section className="section-box wfSectionDark wfPadSection">
<div className="container">
{posts.length === 0 ? (
<div className="row">
<div className="col-lg-12 text-center">
<p className="text-body-lead-large color-gray-600">
No blog posts yet. Add markdown files to the <code>posts/</code> directory to get started.
<p className="wfMutedLg">
No blog posts yet. Add markdown files to the{' '}
<code className="wf-code-inline">posts/</code> directory to get started.
</p>
</div>
</div>
Expand All @@ -55,28 +53,23 @@ export default async function Blog() {
{new Date(post.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
day: 'numeric',
})}
</span>
)}
<Link href={`/blog/${post.slug}`} className="text-heading-4">
{post.title}
</Link>
{post.description && (
<p className="text-body-text color-gray-500 mt-15">
{post.description}
</p>
<p className="text-body-text color-gray-500 mt-15">{post.description}</p>
)}
{post.coverImage && (
<div className="grid-4-img">
<Link href={`/blog/${post.slug}`}>
<img
<BlogCoverImage
src={post.coverImage}
alt={post.title}
width={400}
height={250}
loading="lazy"
style={{ width: '100%', height: 'auto' }}
className="wfImgResponsive"
/>
</Link>
</div>
Expand Down
Loading
Loading