Skip to content
Open
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
41 changes: 41 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "biomejs.biome",
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit"
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[jsonc]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
119 changes: 117 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,117 @@
# sossoldi-website
Website for Sossoldi, the open-source Flutter app for tracking your net worth, expenses, income, and investments
# Sossoldi Landing Page

The official landing page for [Sossoldi](https://github.com/RIP-Comm/Sossoldi) - the open-source personal finance app by the Mr. RIP Community.

## Features

- **Internationalization**: Full English and Italian support with URL prefix routing (`/en`, `/it`)
- **Modern Design**: Glassmorphism UI with the "Serious Blue" Sossoldi brand palette
- **Animations**: Smooth Framer Motion animations including animated counters and graphs
- **Dark/Light Mode**: System-aware theme switching
- **MDX Blog**: Static blog with full MDX support
- **SEO Optimized**: Static generation for all pages

## Tech Stack

- **Framework**: Next.js 16 with App Router
- **Styling**: Tailwind CSS v4 with custom Sossoldi color palette
- **Components**: shadcn/ui (New York style)
- **Animations**: Framer Motion
- **i18n**: next-intl with prefix routing
- **Content**: MDX for blog posts

## Getting Started

```bash
# Install dependencies
bun install

# Run development server
bun dev

# Build for production
bun run build

# Start production server
bun start
```

## Project Structure

```
app/
├── [locale]/
│ ├── layout.tsx # Locale-wrapped layout
│ ├── page.tsx # Landing page
│ └── blog/
│ ├── page.tsx # Blog listing
│ └── [slug]/ # Individual blog posts
├── globals.css # Sossoldi brand colors & styles

components/
├── layout/ # Navbar, Footer, MobileNav, LocaleSwitcher
├── sections/ # Hero, Features, FAQ, BlogPreview
├── ui/ # shadcn/ui + custom components
└── providers/ # Theme provider

content/
└── blog/ # MDX blog posts

i18n/
├── routing.ts # Locale configuration
├── request.ts # next-intl server config
└── navigation.ts # Localized navigation helpers

messages/
├── en.json # English translations
└── it.json # Italian translations
```

## Redirects

The middleware handles external redirects:

| Path | Destination |
|------|-------------|
| `/docs` | GitHub Pages documentation |
| `/contribute` | CONTRIBUTING.md on GitHub |
| `/community` | Discord invite |
| `/repo` | GitHub repository |

## Brand Colors

The Sossoldi "Serious Blue" palette:

- **Primary**: `#0D47A1` (Blue 900)
- **Primary Light**: `#1565C0` (Blue 800)
- **Accent**: `#42A5F5` (Blue 400)
- **Success**: `#4CAF50` (Income/Positive)
- **Destructive**: `#EF5350` (Expense/Negative)

## Adding Blog Posts

Create a new `.mdx` file in `content/blog/`:

```mdx
export const metadata = {
title: "Your Post Title",
publishDate: "2024-12-20",
excerpt: "A brief description of the post.",
author: {
name: "Author Name",
bio: "Short bio",
avatar: "https://github.com/username.png",
},
tags: ["Tag1", "Tag2"],
seo: {
metaTitle: "SEO Title",
metaDescription: "SEO Description",
},
};

Your MDX content here...
```

## License

Open source, built with ❤️ by the Mr. RIP Community.
65 changes: 65 additions & 0 deletions app/[locale]/blog/[slug]/_queries/get-post.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { promises as fs } from "fs";
import { MDXContent } from "mdx/types";
import path from "path";

type Author = {
name: string;
bio: string;
avatar: string;
};

type SEO = {
metaTitle: string;
metaDescription: string;
};

export type PostMetadata = {
title: string;
featuredImage?: string;
publishDate: string;
lastModified?: string;
author: Author;
excerpt: string;
tags: string[];
seo: SEO;
};

export type Post = PostMetadata & {
slug: string;
content: MDXContent;
};

export async function getPostSlugs() {
const postsDirectory = path.join(process.cwd(), "content/blog");
const files = await fs.readdir(postsDirectory);
return files.filter((file) => file.endsWith(".mdx")).map((file) => file.replace(/\.mdx$/, ""));
}

export async function getPost(slug: string): Promise<Post | null> {
try {
const post = await import(`@/content/blog/${slug}.mdx`);

return {
slug,
...post.metadata,
content: post.default,
} as Post;
} catch (error) {
console.error("Error loading blog post:", error);
return null;
}
}

export async function getAllPosts(): Promise<Post[]> {
const slugs = await getPostSlugs();
const posts = await Promise.all(
slugs.map(async (slug) => {
const post = await getPost(slug);
return post;
}),
);

return posts
.filter((post): post is Post => post !== null)
.sort((a, b) => new Date(b.publishDate).getTime() - new Date(a.publishDate).getTime());
}
Loading