Skip to content

Commit fb66a5d

Browse files
feat: add guidelines to landing page
1 parent b5e00e3 commit fb66a5d

18 files changed

+581
-539
lines changed

.env.example

+13-8
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1-
SECRET_KEY=
1+
# Copy this file to .env and fill in the values
22

3-
CDN_NAME=
4-
CDN_API_KEY=
5-
CDN_API_SECRET=
3+
# Generate a secrety key for Django and set it here.
4+
# You can use the following command to generate a secret key:
5+
# python3 -c "import secrets; print(secrets.token_urlsafe())"
6+
SECRET_KEY=
67

8+
# Set the following variables to the values of your database
79
DB_HOST=
810
DB_NAME=
911
DB_USER=
1012
DB_PASSWORD=
1113
DB_PORT=
1214

15+
# Set the following variables to the values of your Cloudinary account
16+
CDN_NAME=
17+
CDN_API_KEY=
18+
CDN_API_SECRET=
19+
20+
# Set the following variables to the values of your SMTP server
1321
SMTP_HOST_USER=
1422
SMTP_HOST_PASSWORD=
1523

24+
# Wether to run the application in test mode or not
1625
TEST=
17-
18-
TWILIO_ACCOUNT_SID=
19-
TWILIO_AUTH_TOKEN=
20-
TWILIO_WPP_NUMBER=

README.md

+49-69
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,31 @@
33

44
<p align="center">
55
<img alt="django-react-typescript logo" src="assets/Logo.png" />
6-
<p align="center">Fully-featured, Django v5 + React v18 boilerplate with great DX.</p>
6+
<p align="center">Fully-featured, Django 5 + React 18 boilerplate with great DX.</p>
77
</p>
88

99
---
1010

11-
This is a fully-featured Django + React boilerplate built for great development experience and easy deployment guidelines.
11+
This is an opinionated fully-featured Django + React boilerplate built for great development experience and easy deployment guidelines.
12+
13+
It is ideal if you want to bootstrap a blog or a portfolio website quickly, or even a more complex application that requires a backend and a frontend, while leveraging the best from React and Django.
14+
15+
- [Getting started](#getting-started)
16+
- [Setting up a database](#setting-up-a-database)
17+
- [Setting up a CDN](#setting-up-a-cdn)
18+
- [Running the project](#running-the-project)
19+
- [Application architecture \& features](#application-architecture--features)
20+
- [Frontend](#frontend)
21+
- [Backend](#backend)
22+
- [Integrations](#integrations)
23+
- [Infrastructure \& deployment](#infrastructure--deployment)
24+
- [Virtualized Deploy Workflow](#virtualized-deploy-workflow)
25+
- [Bare-metal Deploy Workflow](#bare-metal-deploy-workflow)
26+
- [Configuration](#configuration)
27+
- [Global](#global)
28+
- [Exclusively used in production](#exclusively-used-in-production)
29+
- [Frontend](#frontend-1)
30+
- [Exclusively used in production](#exclusively-used-in-production-1)
1231

1332
## Getting started
1433

@@ -30,6 +49,10 @@ For convenience, if you want to use Docker + Docker Compose to spin up a Postgre
3049
pnpm run dev:db:up
3150
```
3251

52+
### Setting up a CDN
53+
54+
This project uses Cloudinary as a CDN, so you will need to have an account on Cloudinary and set up the `.env` file with the correct credentials. Use the [`.env.example`](./.env.example) file as a reference.
55+
3356
### Running the project
3457

3558
Once you've set up the database, you can start the project by running one of:
@@ -41,7 +64,7 @@ pnpm dev:full # Starts the project while assuming you've setup a database using
4164

4265
By default, the frontend app will run on `localhost:4000` and the backend app will run on `localhost:8000`. If you're running the containerized Postgres, it will run on `localhost:5432` and pgAdmin will run on `localhost:5050`.
4366

44-
## Application architecture
67+
## Application architecture & features
4568

4669
This application's architect is quite simple and leverages the best of both Django and React. On a nutshell, React and Django integrate through Django's Views and Django Rest Framework's API endpoints.
4770

@@ -60,67 +83,42 @@ flowchart TD
6083
n0 -- Consumes API Key\nto authenticate\nwith backend --> ng
6184
```
6285

63-
### Global
64-
65-
- Commit lint rules
86+
Below you will find the stack used for each part of the application and the features that are already implemented.
6687

6788
### Frontend
6889

69-
- [React](https://reactjs.org/)
70-
- [Typescript](https://www.typescriptlang.org/)
71-
- [React Router](https://reactrouter.com/)
72-
- [Webpack](https://webpack.js.org/)
73-
74-
| Other features | Status |
75-
| --------------------------- | ----------- |
76-
| SSR ready | In progress |
77-
| Service workers | ✔️ |
78-
| Gzip static file gen | ✔️ |
79-
| Cache control | ✔️ |
80-
| Code split and lazy loading | ✔️ |
81-
| Google Analytics ready | ✔️ |
82-
| PWA ready | ✔️ |
90+
Stack:
91+
- React 18
92+
- React Router 6
93+
- Typescript 5
94+
- Webpack 5
95+
- Tailwind CSS 3
8396

8497
### Backend
8598

86-
- [Django](https://www.djangoproject.com/)
87-
- [Django REST Framework](https://www.django-rest-framework.org/)
88-
- Django CORS Headers
99+
Stack:
100+
- Django 5
101+
- Django Rest Framework
102+
- Postgres
89103

90-
| Other features | Status |
91-
| -------------------- | ------ |
92-
| Token authentication | ✔️ |
93-
| SMTP ready | ✔️ |
104+
### Integrations
94105

95-
### Infrastructure
106+
- [Sentry](https://sentry.io/welcome/)
107+
- [Cloudinary](https://cloudinary.com/)
96108

97-
- Docker image featuring
98-
- [Memcached](https://memcached.org/)
99-
- [PostgreSQL](https://www.postgresql.org/)
100-
- [Supervisor](http://supervisord.org/) (optional, should be used if you're deploying on a non-virtualized system)
109+
## Infrastructure & deployment
101110

102-
| Other features | Status |
103-
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
104-
| NGINX config file | ✔️ |
105-
| CI/CD to any V.M. (AWS EC2s, GCloud apps, Digital Ocean droplets, Hostgator VPSs, etc) accessible via SSH (the `hml` and `prd` branches will trigger the [deploy workflow](#Virtualized-Deploy-Workflow)) | ✔️ |
106-
| CI/CD to deploy straight on host (without virtualization; not recommended) (the branch `prd-host` will trigger this. See more on the [host deploy workflow](#Host-Deploy-Workflow) method) | ✔️ |
111+
Although this project provides some guidelines on how to deploy the app, it is not mandatory to follow them. You can deploy the app on any platform you want, as long as it supports Docker and Docker Compose, or even deploy the app on a bare-metal machine.
107112

108-
### Integrations
113+
This codebase has two deploy methods available via GitHub actions:
109114

110-
- [Sentry](https://sentry.io/welcome/)
111-
- [Cloudinary](https://cloudinary.com/)
112-
- [Twilio](https://www.twilio.com/)
113-
- [Google Analytics](https://analytics.google.com/analytics/web/)
115+
### Virtualized Deploy Workflow
116+
117+
The `virtualized-deploy-qa` and `virtualized-deploy-prod` branches will trigger this wokflow. You can use it to deploy the app to any Virtual Machine accessible via SSH (AWS EC2s, GCloud apps, Digital Ocean droplets, Hostgator VPSs, etc), and you would likely want to change the name of these branches to something more meaningful to your project.
114118

115-
## Development directions
119+
### Bare-metal Deploy Workflow
116120

117-
1. Clone this repo: `git clone https://github.com/marcelovicentegc/django-react-typescript.git`
118-
2. Create a virtual environment: `python -m venv venv`
119-
3. Activate it ☝️: `source venv/bin/activate` or `venv\Scripts\activate` if you're on a Windows
120-
4. Install dependencies: `npm i && pip install -r requirements.txt && cd frontend && npm i`
121-
5. Setup the project `.env` file by taking as example the `.env.example` on the root folder (refer to [configuration](#Configuration) for more details)
122-
6. Setup the frontend app's `frontend/.env` file by taking as example the `frontend/.env.example` file (refer to [configuration](#Configuration) for more details)
123-
7. Start the application: `npm start` (make sure Postgres is up and running)
121+
The `bare-metail-deploy-qa` and `bare-metal-deploy-prod` branches will trigger this workflow. You can use it to deploy the app straight on the host machine, without any virtualization. This is not recommended, but ou never know when you will need to deploy an app on a bare-metal machine 🤷‍♀️
124122

125123
## Configuration
126124

@@ -142,9 +140,6 @@ You should configure these variables on a `.env` file on the root folder for the
142140
| SMTP_HOST_USER | | Your SMTP email (should be a GMail one) |
143141
| SMTP_HOST_PASSWORD | - | Your SMTP email password |
144142
| TEST | 0 | Used to test the app on the pipeline |
145-
| TWILIO_ACCOUNT_SID | - | Your Twilio account SID (**optional**) |
146-
| TWILIO_AUTH_TOKEN | - | Your Twilio account Auth token(**optional**) |
147-
| TWILIO_WPP_NUMBER | - | Your Twilio account's Whatsapp number (**optional**) |
148143

149144
#### Exclusively used in production
150145

@@ -167,7 +162,7 @@ You should configure these variables on a `.env` file on the root folder for the
167162
| -------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
168163
| NODE_ENV | `development` | Let's Webpack know when to build files to correct public path, optimize code and when to prepend localhost for API endpoints or not. Values must be either `development` or `production`. This is hardcoded on the [Dockerfile](./Dockerfile) |
169164
| AUTH_TOKEN | - | An auth key generated on Django's admin that must be associated to a user with specific permissions (i.g.: read specific infos from Django's ORM) |
170-
| GTAG_ID | - | Google Analytics ID |
165+
| |
171166

172167
#### Exclusively used in production
173168

@@ -176,18 +171,3 @@ You should configure these variables on a `.env` file on the root folder for the
176171
| HML_AUTH_KEY | Same as AUTH_KEY but for a HML environment |
177172
| HML_GTAG_ID | Same as GTAG_ID but for a HML environment |
178173

179-
## Deployment worfklows
180-
181-
### Virtualized Deploy Workflow
182-
183-
Branches `hml` and `prd` will trigger this workflow.
184-
185-
![Deploy workflow](./assets/DeployWorkflow.jpg)
186-
187-
### Host Deploy Workflow
188-
189-
For this kind of deploy to work, you will need a running Postgres database, Nginx, and Supervisor processes.
190-
191-
## Basic architecture
192-
193-
![Architecture](./assets/Architecture.png)

backend/admin/publications.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
from django.contrib import admin
2-
from unfold.admin import ModelAdmin
32
from django.utils.html import format_html
43
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
54
from backend.models.publications import Publication
65
from backend.actions import ExportCsvMixin
76
from backend.utils import Strings
87

98
@admin.register(Publication)
10-
class PublicationAdmin(ModelAdmin, DynamicArrayMixin):
9+
class PublicationAdmin(admin.ModelAdmin, DynamicArrayMixin):
1110
def image_preview(self, obj):
1211
return format_html('<img src="{}" style="height: 150px" />'.format(obj.image.url))
1312

backend/admin/subscribers.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from django.contrib import admin
2-
from unfold.admin import ModelAdmin
32
from backend.models.subscribers import Subscriber
43
from backend.actions import ExportCsvMixin
54

65

76
@admin.register(Subscriber)
8-
class SubscriberAdmin(ModelAdmin, ExportCsvMixin):
7+
class SubscriberAdmin(admin.ModelAdmin, ExportCsvMixin):
98
list_filter = ('contact_method', 'created_at')
109
actions = ["export_as_csv"]
1110
search_fields = ['name']

frontend/.env.example

-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
NODE_ENV=development
22
AUTH_TOKEN=
3-
GTAG_ID=

frontend/lib/api/use-api.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@ import {
88
import { getSecrets } from "../config";
99
import type { IGetPaginatedPublicationsResponse, IPublication } from "./types";
1010

11-
const { NODE_ENV, AUTH_TOKEN } = getSecrets();
11+
const { isProd, authToken } = getSecrets();
1212

13-
const PRODUCTION_MODE = NODE_ENV === "production";
1413
const LOCAL_API_URL = "http://localhost:8000";
1514

1615
export function useApi() {
1716
const getHeaders = new Headers({
1817
Accept: "*/*",
1918
"Accept-Encoding": "gzip, deflate, br",
20-
Authorization: "Token " + AUTH_TOKEN,
19+
Authorization: "Token " + authToken,
2120
});
2221

2322
async function getPublications(
@@ -32,7 +31,7 @@ export function useApi() {
3231
const endpoint = (() => {
3332
const shouldFilter = args.tag && args.title;
3433

35-
if (PRODUCTION_MODE) {
34+
if (isProd) {
3635
if (shouldFilter) {
3736
return getFilteredPublicationsEndoint(args);
3837
}
@@ -69,7 +68,7 @@ export function useApi() {
6968
}): Promise<IGetPaginatedPublicationsResponse> {
7069
const endpoint = (() => {
7170
if (!args) {
72-
if (PRODUCTION_MODE) {
71+
if (isProd) {
7372
return getPaginatedPublicationsEndpoint;
7473
}
7574

@@ -81,7 +80,7 @@ export function useApi() {
8180
}
8281

8382
if (args.page && !args.filter) {
84-
if (PRODUCTION_MODE) {
83+
if (isProd) {
8584
return getPaginatedPublicationsEndpoint + `?page=${args.page}`;
8685
}
8786

@@ -91,7 +90,7 @@ export function useApi() {
9190
`?page=${args.page}`
9291
);
9392
} else if (args.filter) {
94-
if (PRODUCTION_MODE) {
93+
if (isProd) {
9594
return getPaginatedFilteredPublicationsEndoint({
9695
title: args.filter.title,
9796
tag: args.filter.tags,
@@ -122,7 +121,7 @@ export function useApi() {
122121

123122
async function getPublication(slug: string): Promise<IPublication> {
124123
return fetch(
125-
PRODUCTION_MODE
124+
isProd
126125
? getPublicationEndpoint(slug)
127126
: LOCAL_API_URL + getPublicationEndpoint(slug),
128127
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from "react";
2+
import { Button, Card } from "flowbite-react";
3+
import { IPublication } from "../api";
4+
import dayjs from "dayjs";
5+
import { ROUTES, useRouter } from "../routes";
6+
7+
interface Props {
8+
data: IPublication;
9+
}
10+
11+
export function BlogPostPreview(props: Props) {
12+
const { data } = props;
13+
const { push } = useRouter();
14+
15+
return (
16+
<Card
17+
className="max-w-sm mt-4"
18+
imgAlt={data.image_description}
19+
imgSrc={data.image}
20+
>
21+
<h5 className="text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
22+
{data.title} {dayjs(data.created_at).locale("pt-br").format("LLLL")}
23+
</h5>
24+
<p className="font-normal text-gray-700 dark:text-gray-400">
25+
{getPreview(data)}
26+
</p>
27+
<Button onClick={() => push(ROUTES.BLOG + "/" + data.slug)}>
28+
Read more
29+
<svg
30+
className="-mr-1 ml-2 h-4 w-4"
31+
fill="currentColor"
32+
viewBox="0 0 20 20"
33+
xmlns="http://www.w3.org/2000/svg"
34+
>
35+
<path
36+
fillRule="evenodd"
37+
d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
38+
clipRule="evenodd"
39+
/>
40+
</svg>
41+
</Button>
42+
</Card>
43+
);
44+
}
45+
46+
function getPreview(data: IPublication) {
47+
const preview = data.description ? data.description : data.body;
48+
49+
return preview.slice(0, 50);
50+
}

0 commit comments

Comments
 (0)