Skip to content

Commit

Permalink
Merge pull request #45 from ItaloMedici/feature/stripe
Browse files Browse the repository at this point in the history
Feature/stripe
  • Loading branch information
ItaloMedici authored Feb 10, 2025
2 parents 8ba59f5 + eab10c6 commit ddb07da
Show file tree
Hide file tree
Showing 79 changed files with 2,973 additions and 353 deletions.
9 changes: 6 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=SOME_RANDOM_SECRET

DATABASE_URL="DATABASE_URL"
DATABASE_URL="postgres://user:password@host:5432/dbname?sslmode=require"

NEXT_PUBLIC_SITE_URL=http://localhost:3000
SITE_URL=http://localhost:3000

GOOGLE_CLIENT_ID=GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET=GOOGLE_CLIENT_SECRET

MAXIMUM_PLAYERS_PER_BOARD=99999
STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
STRIPE_PUBLISH_KEY=STRIPE_PUBLISH_KEY
STRIPE_WEBHOOK_SECRET=STRIPE_WEBHOOK_SECRET
FREE_PLAN_PRICE_ID=FREE_PLAN_PRICE_ID
12 changes: 8 additions & 4 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ jobs:
- name: Deploy to EC2
run: |
ssh -o StrictHostKeyChecking=no ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF'
sudo docker pull ${{ secrets.DOCKER_IMAGE }}
sudo systemctl start docker
sudo docker stop pontim
sudo docker remove pontim
sudo docker stop pontim || true
sudo docker rm pontim || true
sudo docker pull ${{ secrets.DOCKER_IMAGE }}
sudo docker run --platform linux/arm64 -d --name pontim -p 80:3000 ${{ secrets.DOCKER_IMAGE }}
sudo docker run --platform linux/arm64 --name pontim -p 80:3000 ${{ secrets.DOCKER_IMAGE }}
sudo docker ps
sudo docker logs pontim
sudo docker image prune -f
EOF
20 changes: 11 additions & 9 deletions .github/workflows/publish-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ name: Create and publish a Docker image

on:
push:
branches:
- 'main'
tags:
- 'v*'
branches:
- "main"
tags:
- "v*"
workflow_dispatch:


env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
Expand Down Expand Up @@ -50,11 +49,14 @@ jobs:
"NEXTAUTH_URL=${{ secrets.NEXTAUTH_URL }}"
"NEXTAUTH_SECRET=${{ secrets.NEXTAUTH_SECRET }}"
"DATABASE_URL=${{ secrets.DATABASE_URL }}"
"NEXT_PUBLIC_SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }}"
"SITE_URL=${{ secrets.SITE_URL }}"
"GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}"
"GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}"
"MAXIMUM_PLAYERS_PER_BOARD=${{ secrets.MAXIMUM_PLAYERS_PER_BOARD }}"
STRIPE_SECRET_KEY=${{ secrets.STRIPE_SECRET_KEY }}
STRIPE_PUBLISH_KEY=${{ secrets.STRIPE_PUBLISH_KEY }}
STRIPE_WEBHOOK_SECRET=${{ secrets.STRIPE_WEBHOOK_SECRET }}
FREE_PLAN_PRICE_ID=${{ secrets.FREE_PLAN_PRICE_ID }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
Expand All @@ -64,4 +66,4 @@ jobs:
deploy:
uses: ./.github/workflows/deploy.yml
needs: build-and-push-image
secrets: inherit
secrets: inherit
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
# Changelog

# [0.7.0](https://github.com/ItaloMedici/pontim/compare/v0.6.3...v0.7.0) (2025-02-10)


### Bug Fixes

* change router path ([cd0147f](https://github.com/ItaloMedici/pontim/commit/cd0147f229c4f238e8a9be129b22b10d58a89752))
* **webhook:** rename route ([929602a](https://github.com/ItaloMedici/pontim/commit/929602a9e1de3872695317f4aadb443a480af2d7))


### Features

* add animations to the component how does this work ([afdf970](https://github.com/ItaloMedici/pontim/commit/afdf970fd277d5afc257730b0a1af310533e4e06))
* add aria label to arrow icon ([a671696](https://github.com/ItaloMedici/pontim/commit/a6716966c37d4fcf04ee4a2d2b3e29ce639aa8e6))
* add plan name on jwt ([ee8b5b9](https://github.com/ItaloMedici/pontim/commit/ee8b5b943897eca147f1f63f66ddc922d24091de))
* authorize webhook url ([9b9c667](https://github.com/ItaloMedici/pontim/commit/9b9c66739276938e8c09e6e471a2e7e95dbbd727))
* create footer component ([02d1ff8](https://github.com/ItaloMedici/pontim/commit/02d1ff8072d035aa223b10897be43ee4176e2f16))
* create home dialog messages ([a0398e6](https://github.com/ItaloMedici/pontim/commit/a0398e6d8d2f295e26a956d10804729e24fd31df))
* create how it works component ([919b624](https://github.com/ItaloMedici/pontim/commit/919b6240ac15c0c3578136c760801770358f8fa0))
* create loggout page ([15cbaab](https://github.com/ItaloMedici/pontim/commit/15cbaabbe43e9cdbc564a978795f7a1e222d95e3))
* create pricing page ([e8b5f53](https://github.com/ItaloMedici/pontim/commit/e8b5f53e06827a54563dec0ebeac432e4b24e5d0))
* create room is full page ([e1ee63a](https://github.com/ItaloMedici/pontim/commit/e1ee63a2287aa0098f78f145c486ee7930095b3d))
* **marketing:** improve homepage ([3defd1f](https://github.com/ItaloMedici/pontim/commit/3defd1f1359b1a544e7c6ea225a0a2014f7971e5))
* setup stripe ([6eb5675](https://github.com/ItaloMedici/pontim/commit/6eb567590502e66f3f52b92cd6d16866c1b55a36))
* **stripe:** add board rounds limit ([1bb5b70](https://github.com/ItaloMedici/pontim/commit/1bb5b70a6d84344caeff19a119c0f92dbf366a08))
* **stripe:** add webhook events handling ([5c176a1](https://github.com/ItaloMedici/pontim/commit/5c176a102d823ae62ff170aba469398e4c479009))
* **stripe:** create customer when new user signIn ([294939c](https://github.com/ItaloMedici/pontim/commit/294939c65d11636cb929ff08d5ca01729b8e086b))
* **stripe:** create plan, subscription and user tables ([e78ef05](https://github.com/ItaloMedici/pontim/commit/e78ef05d359674e5373c378f04e00bb00cbd5f4a))


### Performance Improvements

* **marketing:** change hero image png to webp ([8a4459a](https://github.com/ItaloMedici/pontim/commit/8a4459a618e00ec18f912e16f73b11a9f8974828))

## [0.6.3](https://github.com/ItaloMedici/pontim/compare/v0.6.2...v0.6.3) (2025-01-27)

## [0.6.2](https://github.com/ItaloMedici/pontim/compare/v0.6.1...v0.6.2) (2025-01-26)
Expand Down
14 changes: 10 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,23 @@ COPY . .
RUN --mount=type=secret,id=NEXTAUTH_URL \
--mount=type=secret,id=NEXTAUTH_SECRET \
--mount=type=secret,id=DATABASE_URL \
--mount=type=secret,id=NEXT_PUBLIC_SITE_URL \
--mount=type=secret,id=SITE_URL \
--mount=type=secret,id=GOOGLE_CLIENT_ID \
--mount=type=secret,id=GOOGLE_CLIENT_SECRET \
--mount=type=secret,id=MAXIMUM_PLAYERS_PER_BOARD \
--mount=type=secret,id=STRIPE_SECRET_KEY \
--mount=type=secret,id=STRIPE_PUBLISH_KEY \
--mount=type=secret,id=STRIPE_WEBHOOK_SECRET \
--mount=type=secret,id=FREE_PLAN_PRICE_ID \
sh -c 'echo "NEXTAUTH_URL=$(cat /run/secrets/NEXTAUTH_URL)" > .env && \
echo "NEXTAUTH_SECRET=$(cat /run/secrets/NEXTAUTH_SECRET)" >> .env && \
echo "DATABASE_URL=$(cat /run/secrets/DATABASE_URL)" >> .env && \
echo "NEXT_PUBLIC_SITE_URL=$(cat /run/secrets/NEXT_PUBLIC_SITE_URL)" >> .env && \
echo "SITE_URL=$(cat /run/secrets/SITE_URL)" >> .env && \
echo "GOOGLE_CLIENT_ID=$(cat /run/secrets/GOOGLE_CLIENT_ID)" >> .env && \
echo "GOOGLE_CLIENT_SECRET=$(cat /run/secrets/GOOGLE_CLIENT_SECRET)" >> .env && \
echo "MAXIMUM_PLAYERS_PER_BOARD=$(cat /run/secrets/MAXIMUM_PLAYERS_PER_BOARD)" >> .env'
echo "STRIPE_SECRET_KEY=$(cat /run/secrets/STRIPE_SECRET_KEY)" >> .env && \
echo "STRIPE_PUBLISH_KEY=$(cat /run/secrets/STRIPE_PUBLISH_KEY)" >> .env && \
echo "STRIPE_WEBHOOK_SECRET=$(cat /run/secrets/STRIPE_WEBHOOK_SECRET)" >> .env && \
echo "FREE_PLAN_PRICE_ID=$(cat /run/secrets/FREE_PLAN_PRICE_ID)" >> .env'

RUN npm run db:generate

Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Ítalo Médici

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
94 changes: 73 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,88 @@
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).
<div align="center">
<img src="https://i.imgur.com/IZBsCgh.png" alt="Pontim Logo" width="250"/>

# 🃏 Pontim - The Open-Source Scrum Poker Platform

[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE) [![Version](https://img.shields.io/github/v/release/ItaloMedici/pontim)](https://github.com/ItaloMedici/pontim/releases) [![Contributors](https://img.shields.io/github/contributors/ItaloMedici/pontim)](https://github.com/ItaloMedici/pontim/graphs/contributors) [![Issues](https://img.shields.io/github/issues/ItaloMedici/pontim)](https://github.com/ItaloMedici/pontim/issues)

## Getting Started
</div>

First, run the development server:
**Pontim** is an open-source, interactive Scrum Poker platform that helps agile teams estimate story points collaboratively and in real time. 🚀
It offers an engaging and efficient way to vote on story complexity using **Fibonacci numbers**, with a unique interactive experience where players can grab attention using sounds. 🔊

```bash
## ✨ Features

✔️ **Live Voting** – Players can vote in real-time and see the average selection.
✔️ **Interactive Notifications** – Grab teammates' attention with sound alerts.
✔️ **Customizable Rooms** – Create rooms with different limits based on your plan.
✔️ **Google OAuth Authentication** – Quick and easy login with Google.
✔️ **Stripe Integration** – Subscription-based plans for premium features.

## 🛠️ Tech Stack

Pontim is built with modern technologies to ensure a seamless and scalable experience:

| Tech | Purpose |
|---------------------|-----------------------------------------------|
| **Next.js** | Frontend framework for server-side rendering |
| **React** | UI library for interactive components |
| **Prisma** | ORM for PostgreSQL database |
| **PostgreSQL** | Relational database for persistent storage |
| **NextAuth** | Authentication provider using Google OAuth |
| **Stripe** | Payment gateway for subscriptions |
| **Docker** | Containerization for deployment |

## 🚀 Getting Started

### 1 - **Clone the Repository**
```sh
git clone https://github.com/ItaloMedici/pontim.git
cd pontim
```

### 2 - **Install Dependencies**
```sh
npm install
```

### 3 - **Set Up Environment Variables**
Create a `.env.exemple` file in the root folder and add the necessary variables:
```ini
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-secret
DATABASE_URL=your-db-url
...
```

### 4 - **Run the Project Locally**
```sh
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Visit `http://localhost:3000` to start using Pontim! 🎉

---

## 🤝 Contributing

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
We welcome contributions from the community! To contribute:

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
1. **Fork the repository** and create a new branch.
2. Implement your feature or fix.
3. Submit a **Pull Request (PR)** with a detailed description.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
---

## Learn More
## 🐝 License

To learn more about Next.js, take a look at the following resources:
Pontim is open-source and released under the **MIT License**. See [LICENSE](LICENSE) for details.

- [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!
## 📚 Learning Purpose

## Deploy on Vercel
This project is designed as a **learning experience** for building **SaaS applications**, working with **real-time interactions**, and implementing **scalable architectures**. Feel free to explore, contribute, and improve Pontim! 🚀

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/deployment) for more details.
👨‍💻 **Developed by:** [Ítalo Médici](https://github.com/ItaloMedici)
🌟 **Star this project** if you find it useful! 🚀
10 changes: 10 additions & 0 deletions app/(auth)/logout/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use client";

import { LoadingLogo } from "@/components/loading-logo/loading";
import { Suspense } from "react";

const LogoutLayout = ({ children }) => {
return <Suspense fallback={<LoadingLogo />}>{children}</Suspense>;
};

export default LogoutLayout;
21 changes: 21 additions & 0 deletions app/(auth)/logout/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client";

import { signOut } from "next-auth/react";
import { useSearchParams } from "next/navigation";
import { useEffect } from "react";

const LogoutPage = () => {
const searchParams = useSearchParams();
const callbackUrl = searchParams.get("callbackUrl") || "/";

useEffect(() => {
signOut({
redirect: true,
callbackUrl,
});
});

return null;
};

export default LogoutPage;
40 changes: 38 additions & 2 deletions app/(dashboard)/home/_components/create-room-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { toast } from "@/components/toast";
import { Button } from "@/components/ui/button";
import {
Dialog,
Expand All @@ -8,12 +9,47 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { useAction } from "@/hooks/use-action";
import { SearchParams } from "@/lib/consts";
import { ValidationState } from "@/messages/state";
import { canAddMoreRoom } from "@/use-cases/plan/can-add-more-room";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { CreateRoomForm } from "./create-room-form";

export function CreateRoomDialog({ trigger }: { trigger?: React.ReactNode }) {
const [open, setOpen] = useState(false);
const { mutation, isPending } = useAction(canAddMoreRoom);
const router = useRouter();

const onOpenChange = async (_open: boolean) => {
if (!_open) {
setOpen(false);
return;
}

try {
const allowed = await mutation({});

if (!allowed) {
router.replace(
`/home?${SearchParams.STATE}=${ValidationState.USER_REACH_MAX_ROOMS}`,
);
return;
}

setOpen(true);
} catch (error) {
console.error(error);
toast.error("Algo deu errado ao tentar criar a sala", {
icon: "😢",
});
}
};

return (
<Dialog>
<DialogTrigger asChild>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTrigger asChild disabled={isPending}>
{trigger ?? <Button size="lg">Criar nova sala</Button>}
</DialogTrigger>
<DialogContent>
Expand Down
18 changes: 18 additions & 0 deletions app/(dashboard)/home/_components/messages/errors-messages.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use client";

import { toast } from "@/components/toast";
import { useSearchParams } from "next/navigation";
import { useEffect } from "react";

export const ErrorsMessages = () => {
const searchParams = useSearchParams();
const error = searchParams.get("error");

useEffect(() => {
if (typeof error === "string") {
toast.error(error);
}
}, [error]);

return null;
};
Loading

0 comments on commit ddb07da

Please sign in to comment.