Compare commits
336
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f059ca37da | ||
|
|
3b359dd3cc | ||
|
|
318db9322f | ||
|
|
9d3e600eae | ||
|
|
2184c11448 | ||
|
|
a57206f8b2 | ||
|
|
35afa4a755 | ||
|
|
a1f3187bc7 | ||
|
|
a5cac66c3f | ||
|
|
509b6b5e8b | ||
|
|
7f508fc04b | ||
|
|
974cee1cf4 | ||
|
|
6240a4104a | ||
|
|
f51f6daad3 | ||
|
|
c72685679c | ||
|
|
15700ae304 | ||
|
|
174a163cd3 | ||
|
|
2ebb81a981 | ||
|
|
225d45307a | ||
|
|
cb26b8edcd | ||
|
|
4a95f6e2d1 | ||
|
|
d8438e2d84 | ||
|
|
95243557c6 | ||
|
|
4be030ec28 | ||
|
|
816eac8470 | ||
|
|
37d0359dae | ||
|
|
75fe3cdcf5 | ||
|
|
dc4618383e | ||
|
|
2c79904102 | ||
|
|
12788566f5 | ||
|
|
2234ce1517 | ||
|
|
2e6cfc7107 | ||
|
|
f260c33f97 | ||
|
|
fe6e0368fc | ||
|
|
d64bf301dd | ||
|
|
46e53c7623 | ||
|
|
5400495176 | ||
|
|
499928753c | ||
|
|
3e5c6d707b | ||
|
|
7d2e4f6598 | ||
|
|
92fdc3489e | ||
|
|
9b3f159a09 | ||
|
|
ab1f54d573 | ||
|
|
f6debe5f56 | ||
|
|
36703434d4 | ||
|
|
ca7e679ebb | ||
|
|
d8429c43a6 | ||
|
|
2a5dd51d5b | ||
|
|
5e9cbcf5c8 | ||
|
|
496702f2ab | ||
|
|
07a19d407e | ||
|
|
5df7cd72c8 | ||
|
|
9793bdaab9 | ||
|
|
e062d42e66 | ||
|
|
254cdcfda7 | ||
|
|
ccd03f11c7 | ||
|
|
12cb58b891 | ||
|
|
80a6430dca | ||
|
|
aae8684fcb | ||
|
|
bd3c55cba7 | ||
|
|
bdbc6eea71 | ||
|
|
587c1584d9 | ||
|
|
d3ae275d8c | ||
|
|
a616c40a64 | ||
|
|
d836455d64 | ||
|
|
e5daed0aa8 | ||
|
|
262be0bf04 | ||
|
|
08eac2834d | ||
|
|
892bbe0726 | ||
|
|
9eaf677a81 | ||
|
|
a5536dcaf2 | ||
|
|
c9f89cbfa5 | ||
|
|
266cefce0d | ||
|
|
cb905ece81 | ||
|
|
f8b81ed520 | ||
|
|
56d817ed34 | ||
|
|
7bd3a6f06a | ||
|
|
5ac061ca7d | ||
|
|
fd20d085ba | ||
|
|
a6d794000a | ||
|
|
c353c228c8 | ||
|
|
7872c6dbd6 | ||
|
|
b82c608838 | ||
|
|
51f8971604 | ||
|
|
3363396db3 | ||
|
|
4987d40678 | ||
|
|
2e2aa10362 | ||
|
|
5b21612005 | ||
|
|
6478140b11 | ||
|
|
e040b61482 | ||
|
|
7f257d9027 | ||
|
|
f84728ba74 | ||
|
|
736cfc63f1 | ||
|
|
d67f6edfc1 | ||
|
|
f2ee5feaf4 | ||
|
|
0030eb5a9e | ||
|
|
7b177bcdc7 | ||
|
|
35c5feb7c9 | ||
|
|
87fb54aeae | ||
|
|
aa3dda41b5 | ||
|
|
b55df10410 | ||
|
|
5ccf5071fa | ||
|
|
c9574334a4 | ||
|
|
dad604fed6 | ||
|
|
2b5eacb477 | ||
|
|
1cc9614e34 | ||
|
|
90d3e393ea | ||
|
|
bcff4c1c01 | ||
|
|
c8c3d8b32a | ||
|
|
858f649af1 | ||
|
|
e8260e0b14 | ||
|
|
dba527f88a | ||
|
|
d5b902dded | ||
|
|
9e0ea84eda | ||
|
|
b0ae4240d6 | ||
|
|
fc6443975a | ||
|
|
254fda1cf3 | ||
|
|
fa2e026d26 | ||
|
|
3cd0826e29 | ||
|
|
ebe53d43ce | ||
|
|
05e4367f79 | ||
|
|
6835c6e0f2 | ||
|
|
6c14f1cbad | ||
|
|
25f50c90f6 | ||
|
|
b5e8be0e5d | ||
|
|
27dfaa8f6a | ||
|
|
a51b72d7b5 | ||
|
|
af0c6ab782 | ||
|
|
545b28ec90 | ||
|
|
d541cf9b63 | ||
|
|
ce5bf1edf0 | ||
|
|
f5ac068147 | ||
|
|
8d1a80e2a6 | ||
|
|
397c3ca654 | ||
|
|
216b1d7f92 | ||
|
|
1dc5ae2cfb | ||
|
|
0209ddbaf5 | ||
|
|
d2dc4abe58 | ||
|
|
12e981027b | ||
|
|
75dafc4a8d | ||
|
|
e5b049c1ba | ||
|
|
8648860eae | ||
|
|
2b8e08fd19 | ||
|
|
01dd8f9b9f | ||
|
|
e6936416d3 | ||
|
|
34c8e646bb | ||
|
|
42250044a2 | ||
|
|
8e85ef550f | ||
|
|
7c68f879c7 | ||
|
|
83dedb57fb | ||
|
|
e6df1f97bb | ||
|
|
0598b05956 | ||
|
|
e1840b97bd | ||
|
|
84aba41fd9 | ||
|
|
430100c916 | ||
|
|
013212acfc | ||
|
|
a54984fa16 | ||
|
|
5ff7f2dd34 | ||
|
|
fcfb72641b | ||
|
|
f556e95bcb | ||
|
|
d0c1cabc25 | ||
|
|
af714c4678 | ||
|
|
810e6cb26b | ||
|
|
46ee411a72 | ||
|
|
caf2e8b442 | ||
|
|
9849a1d64f | ||
|
|
654b4ce2dc | ||
|
|
4a39673786 | ||
|
|
dca24f1bc0 | ||
|
|
292ec408c8 | ||
|
|
55c74d1573 | ||
|
|
afd494e985 | ||
|
|
2526833f8a | ||
|
|
3040eb95ed | ||
|
|
44058cb3d0 | ||
|
|
7608a95bc4 | ||
|
|
99fd68aa31 | ||
|
|
ce85368029 | ||
|
|
9304f05671 | ||
|
|
ae7a2090c0 | ||
|
|
7351673848 | ||
|
|
b9570c9d09 | ||
|
|
43b5ab5887 | ||
|
|
91252fed2d | ||
|
|
6e082476c1 | ||
|
|
2037640bcd | ||
|
|
f495c83949 | ||
|
|
45968761ca | ||
|
|
313a195edb | ||
|
|
e9e58a08f9 | ||
|
|
f2ca055ee2 | ||
|
|
ecf72fc11d | ||
|
|
e291776598 | ||
|
|
bd392f22f2 | ||
|
|
08150761bf | ||
|
|
97ac8535e9 | ||
|
|
36687f04a8 | ||
|
|
8da9486506 | ||
|
|
e9cd3663cf | ||
|
|
10a15e6181 | ||
|
|
297d627542 | ||
|
|
d500346a91 | ||
|
|
aca6046617 | ||
|
|
f63d6ab3fd | ||
|
|
b358b54de4 | ||
|
|
d096c22911 | ||
|
|
eded296fd8 | ||
|
|
389dee988f | ||
|
|
cf4277c7b2 | ||
|
|
1e43f53148 | ||
|
|
79b90c2f7c | ||
|
|
d1f6324649 | ||
|
|
892ad9f4b7 | ||
|
|
b76e39f597 | ||
|
|
3e8533f5e9 | ||
|
|
1c1acd2129 | ||
|
|
23eb329a31 | ||
|
|
8b1028984e | ||
|
|
ad23edaed1 | ||
|
|
0f83d014f0 | ||
|
|
bd48dcdef6 | ||
|
|
5c78039be0 | ||
|
|
32d398de4b | ||
|
|
1eef88c3d8 | ||
|
|
507c07fb7b | ||
|
|
8e7d0c30fe | ||
|
|
ec06f7e00b | ||
|
|
0f9a1c36f5 | ||
|
|
9f78d9abb4 | ||
|
|
5a65ef73ae | ||
|
|
ebfeca61c5 | ||
|
|
4edfce0815 | ||
|
|
f83cb88bfd | ||
|
|
b7f60e1f47 | ||
|
|
3a2ec92cde | ||
|
|
7afdc0dd92 | ||
|
|
08df56c679 | ||
|
|
e4ccd21a23 | ||
|
|
84a0853f51 | ||
|
|
85694c68e3 | ||
|
|
0f8ffd9343 | ||
|
|
a5983a71ea | ||
|
|
c61734e799 | ||
|
|
e3a9a19b61 | ||
|
|
ff391bf11c | ||
|
|
99a1eded0f | ||
|
|
c2aaf5f951 | ||
|
|
4b7f1a7c82 | ||
|
|
769bf80ca7 | ||
|
|
df7a0b2870 | ||
|
|
297cac4533 | ||
|
|
ac3c1dfde0 | ||
|
|
764b4e5d23 | ||
|
|
317e01c498 | ||
|
|
9e94b582d8 | ||
|
|
1acdb2a58e | ||
|
|
c55f1d6afc | ||
|
|
2c3b26a809 | ||
|
|
fe5a99f370 | ||
|
|
136e4e67cd | ||
|
|
91f6efb772 | ||
|
|
100412c783 | ||
|
|
36678d38b7 | ||
|
|
3a39f967ef | ||
|
|
7244fb1d53 | ||
|
|
bb54d5e30c | ||
|
|
d60357917e | ||
|
|
e48b0d1afe | ||
|
|
0d20e7f738 | ||
|
|
bf16b2b010 | ||
|
|
745db317ce | ||
|
|
1645190643 | ||
|
|
3aacabbb2c | ||
|
|
b1c2315eca | ||
|
|
a3e58fecd6 | ||
|
|
b958e29288 | ||
|
|
d258042f5a | ||
|
|
dc2da20ae9 | ||
|
|
a6047e2ccf | ||
|
|
b35b0705a4 | ||
|
|
5bcfd32e1a | ||
|
|
a321c27376 | ||
|
|
ef3b65b1c0 | ||
|
|
b961ec8268 | ||
|
|
ffae1a02e9 | ||
|
|
d5c4c23500 | ||
|
|
35a01e6754 | ||
|
|
1dfee93bb0 | ||
|
|
88f49952e3 | ||
|
|
75e0a0192d | ||
|
|
21fe6de0ec | ||
|
|
ec0e7601c4 | ||
|
|
5ca25e699a | ||
|
|
4f2ddb4cc8 | ||
|
|
7277712740 | ||
|
|
ab83c4e4f1 | ||
|
|
9053de8f4b | ||
|
|
47e8824f2d | ||
|
|
134820e898 | ||
|
|
364fc363f0 | ||
|
|
21fe4ff02d | ||
|
|
51820b5794 | ||
|
|
db74add82b | ||
|
|
9b275a5a32 | ||
|
|
5e24986681 | ||
|
|
469251742e | ||
|
|
81deb1f31d | ||
|
|
62418bf33e | ||
|
|
68b09c2072 | ||
|
|
57ac98b83b | ||
|
|
1db9a4ed63 | ||
|
|
61d9b49a0b | ||
|
|
48aa001f7f | ||
|
|
8e40da01fc | ||
|
|
f20bf2f8e3 | ||
|
|
2889ccc655 | ||
|
|
d30d3e61b9 | ||
|
|
7f01d39909 | ||
|
|
a4ed5449e9 | ||
|
|
d8c1f15c91 | ||
|
|
acda6ea8c0 | ||
|
|
7a57a69259 | ||
|
|
a4b119614d | ||
|
|
0dfad4431f | ||
|
|
43fc0a402c | ||
|
|
b012574e13 | ||
|
|
01a286d0b6 | ||
|
|
52c351f1f6 | ||
|
|
759a477f31 | ||
|
|
7f53829c77 | ||
|
|
c966a4dba9 | ||
|
|
c7bf11a5ee | ||
|
|
2e4c5f1684 | ||
|
|
f8372774d8 | ||
|
|
a762a63c9d | ||
|
|
9f9445e218 |
@@ -1 +0,0 @@
|
|||||||
/Dockerfile
|
|
||||||
+33
-2
@@ -2,15 +2,46 @@
|
|||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: publish pipeline
|
name: publish pipeline
|
||||||
steps:
|
steps:
|
||||||
- name: publish container
|
- name: publish astro container
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings:
|
||||||
username: 3wordchant
|
username: 3wordchant
|
||||||
password:
|
password:
|
||||||
from_secret: git_autonomic_zone_token_3wc
|
from_secret: git_autonomic_zone_token_3wc
|
||||||
repo: git.autonomic.zone/autonomic-cooperative/justice-equity-technology
|
repo: git.autonomic.zone/ruangrupa/lumbung-kios-astro
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
registry: git.autonomic.zone
|
registry: git.autonomic.zone
|
||||||
|
context: astro
|
||||||
|
dockerfile: astro/Dockerfile
|
||||||
|
target: prod
|
||||||
|
- name: publish payload container
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
username: 3wordchant
|
||||||
|
password:
|
||||||
|
from_secret: git_autonomic_zone_token_3wc
|
||||||
|
repo: git.autonomic.zone/ruangrupa/lumbung-kios-payload
|
||||||
|
auto_tag: true
|
||||||
|
registry: git.autonomic.zone
|
||||||
|
context: payload
|
||||||
|
dockerfile: payload/Dockerfile
|
||||||
|
target: prod
|
||||||
|
- name: deploy stack
|
||||||
|
image: git.coopcloud.tech/coop-cloud/stack-ssh-deploy:latest
|
||||||
|
settings:
|
||||||
|
stack: kios_lumbung_space
|
||||||
|
host: lumbung.space
|
||||||
|
deploy_key:
|
||||||
|
from_secret: drone_ssh_lumbung.space
|
||||||
|
environment:
|
||||||
|
DOMAIN: kios.lumbung.space
|
||||||
|
STACK_NAME: kios_lumbung_space
|
||||||
|
SECRET_PAYLOAD_SECRET_VERSION: v1
|
||||||
|
SECRET_TOKEN_VERSION: v1
|
||||||
|
SECRET_MONGO_PASSWORD_VERSION: v1
|
||||||
|
depends_on:
|
||||||
|
- publish astro container
|
||||||
|
- publish payload container
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- main
|
- main
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
NAME=astroad
|
||||||
|
ASTRO_HOST=localhost:3000
|
||||||
|
PAYLOAD_HOST=payload:3001
|
||||||
|
PAYLOAD_URL=http://localhost:3001
|
||||||
|
PAYLOAD_PORT=3001
|
||||||
|
PAYLOAD_SECRET=supersecretkey
|
||||||
|
MONGODB_URI=mongodb://payload:test@mongo:27017
|
||||||
|
MONGODB_USER=payload
|
||||||
|
MONGODB_PW=test
|
||||||
|
TOKEN=supersecrettoken
|
||||||
|
REPOSITORY=mooxl/astroad
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
MONGODB_URI=mongodb://db:27017
|
|
||||||
DB_NAME=turbopress
|
|
||||||
# # FIXME: this is ignored? 🤔🤔🤔
|
|
||||||
ASTRO_PORT=3000
|
|
||||||
ASTRO_HOST=0.0.0.0
|
|
||||||
HOST=0.0.0.0
|
|
||||||
PAYLOAD_PORT=3001
|
|
||||||
PAYLOAD_HOST=0.0.0.0
|
|
||||||
PAYLOAD_PUBLIC_SERVER_URL=http://jett.localhost:3001
|
|
||||||
PAYLOAD_SECRET=37aecd563edc5550a44bbf1e
|
|
||||||
# PAYLOAD_CONFIG_PATH=src/payload.config.ts
|
|
||||||
S3_ACCESS_KEY_ID=
|
|
||||||
S3_SECRET_ACCESS_KEY=
|
|
||||||
S3_REGION=
|
|
||||||
S3_BUCKET=
|
|
||||||
S3_ENDPOINT=
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
extends: ["custom"],
|
|
||||||
rules: {},
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/astro"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/payload"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
name: Payload update
|
||||||
|
on:
|
||||||
|
repository_dispatch:
|
||||||
|
types: [payload_update]
|
||||||
|
jobs:
|
||||||
|
cancel:
|
||||||
|
name: Cancel Previous Runs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Cancel Previous Runs
|
||||||
|
uses: styfle/cancel-workflow-action@0.11.0
|
||||||
|
with:
|
||||||
|
ignore_sha: true
|
||||||
|
access_token: ${{ github.token }}
|
||||||
|
workflow_id: "payload.yml"
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: cancel
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Trigger build
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.HOST }}
|
||||||
|
username: ${{ secrets.USER }}
|
||||||
|
key: ${{ secrets.KEY }}
|
||||||
|
script: |
|
||||||
|
cd ${{ secrets.PATH }}
|
||||||
|
yarn prod astro
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
name: Code Deployment and Environment Setup
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Run remote SSH commands
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Clone or pull repository
|
||||||
|
uses: appleboy/ssh-action@v0.1.10
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.HOST }}
|
||||||
|
username: ${{ secrets.USER }}
|
||||||
|
key: ${{ secrets.KEY }}
|
||||||
|
script_stop: false
|
||||||
|
script: |
|
||||||
|
if [ -d ${{ secrets.PATH }} ]; then
|
||||||
|
cd ${{ secrets.PATH }}
|
||||||
|
git pull
|
||||||
|
else
|
||||||
|
mkdir -p ${{ secrets.PATH }}
|
||||||
|
cd ${{ secrets.PATH }}
|
||||||
|
git clone git@github.com:${{ github.repository }}.git .
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Update environment variables
|
||||||
|
uses: appleboy/ssh-action@v0.1.10
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.HOST }}
|
||||||
|
username: ${{ secrets.USER }}
|
||||||
|
key: ${{ secrets.KEY }}
|
||||||
|
script_stop: false
|
||||||
|
script: |
|
||||||
|
# Bash function to replace env variables
|
||||||
|
replace_env() {
|
||||||
|
local env_var=$1
|
||||||
|
local new_value=$2
|
||||||
|
awk -v var="${env_var}" -v val="${new_value}" -F '=' '{OFS=FS} $1 == var {$2 = val} 1' .env > .env.tmp && mv .env.tmp .env
|
||||||
|
}
|
||||||
|
|
||||||
|
cd ${{ secrets.PATH }}
|
||||||
|
|
||||||
|
# Replace .env with vars
|
||||||
|
replace_env 'NAME' '${{ vars.NAME }}'
|
||||||
|
replace_env 'ASTRO_HOST' '${{ vars.ASTRO_HOST }}'
|
||||||
|
replace_env 'PAYLOAD_HOST' '${{ vars.PAYLOAD_HOST }}'
|
||||||
|
replace_env 'PAYLOAD_URL' '${{ vars.PAYLOAD_URL }}'
|
||||||
|
replace_env 'PAYLOAD_PORT' '${{ vars.PAYLOAD_PORT }}'
|
||||||
|
|
||||||
|
# Replace .env with secrets
|
||||||
|
replace_env 'PAYLOAD_SECRET' '${{ secrets.PAYLOAD_SECRET }}'
|
||||||
|
replace_env 'MONGODB_URI' 'mongodb://${{ secrets.MONGODB_USER }}:${{ secrets.MONGODB_PW }}@mongo:27017'
|
||||||
|
replace_env 'MONGODB_USER' '${{ secrets.MONGODB_USER }}'
|
||||||
|
replace_env 'MONGODB_PW' '${{ secrets.MONGODB_PW }}'
|
||||||
|
replace_env 'TOKEN' '${{ secrets.TOKEN }}'
|
||||||
|
|
||||||
|
# Replace .env with GitHub repository
|
||||||
|
replace_env 'REPOSITORY' '${{ github.repository }}'
|
||||||
|
|
||||||
|
mkdir -p ./astro
|
||||||
|
cp .env ./astro/.env
|
||||||
|
|
||||||
|
- name: Start Production Services
|
||||||
|
uses: appleboy/ssh-action@v0.1.10
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.HOST }}
|
||||||
|
username: ${{ secrets.USER }}
|
||||||
|
key: ${{ secrets.KEY }}
|
||||||
|
script: |
|
||||||
|
cd ${{ secrets.PATH }}
|
||||||
|
yarn prod payload
|
||||||
|
yarn prod astro
|
||||||
+2
-35
@@ -1,36 +1,3 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
data
|
||||||
|
|
||||||
# dependencies
|
|
||||||
node_modules
|
|
||||||
.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
coverage
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
.next/
|
|
||||||
out/
|
|
||||||
build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
# turbo
|
|
||||||
.turbo
|
|
||||||
|
|
||||||
# vercel
|
|
||||||
.vercel
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
pnpm test && pnpm exec lint-staged
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/build
|
|
||||||
/package
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
|
|
||||||
# Ignore files for PNPM, NPM and YARN
|
|
||||||
pnpm-lock.yaml
|
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
|
||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"trailingComma": "all",
|
|
||||||
"plugins": ["prettier-plugin-astro", "prettier-plugin-svelte"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": "*.astro",
|
|
||||||
"options": {
|
|
||||||
"parser": "astro"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "files": "*.svelte", "options": { "parser": "svelte" } }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Vendored
-6
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"prettier.documentSelectors": ["**/*.astro"],
|
|
||||||
"[astro]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
|
||||||
and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
|
||||||
overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
|
||||||
advances of any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email
|
|
||||||
address, without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community leaders responsible for enforcement at
|
|
||||||
.
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
|
||||||
of actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or
|
|
||||||
permanent ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
|
||||||
the community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.0, available at
|
|
||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
|
||||||
enforcement ladder](https://github.com/mozilla/diversity).
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
https://www.contributor-covenant.org/faq. Translations are available at
|
|
||||||
https://www.contributor-covenant.org/translations.
|
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
FROM node:21-alpine3.18
|
|
||||||
RUN npm i -g pnpm turbo
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN pnpm install
|
|
||||||
ENV PAYLOAD_CONFIG_PATH=src/payload.config.ts
|
|
||||||
ENV PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
|
|
||||||
RUN pnpm build
|
|
||||||
|
|
||||||
CMD ["pnpm", "serve"]
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Max Schmidt
|
||||||
|
|
||||||
|
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.
|
||||||
-382
@@ -1,382 +0,0 @@
|
|||||||
# Mozilla Public License Version 2.0
|
|
||||||
|
|
||||||
1. Definitions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
1.1. "Contributor"
|
|
||||||
means each individual or legal entity that creates, contributes to
|
|
||||||
the creation of, or owns Covered Software.
|
|
||||||
|
|
||||||
1.2. "Contributor Version"
|
|
||||||
means the combination of the Contributions of others (if any) used
|
|
||||||
by a Contributor and that particular Contributor's Contribution.
|
|
||||||
|
|
||||||
1.3. "Contribution"
|
|
||||||
means Covered Software of a particular Contributor.
|
|
||||||
|
|
||||||
1.4. "Covered Software"
|
|
||||||
means Source Code Form to which the initial Contributor has attached
|
|
||||||
the notice in Exhibit A, the Executable Form of such Source Code
|
|
||||||
Form, and Modifications of such Source Code Form, in each case
|
|
||||||
including portions thereof.
|
|
||||||
|
|
||||||
1.5. "Incompatible With Secondary Licenses"
|
|
||||||
means
|
|
||||||
|
|
||||||
(a) that the initial Contributor has attached the notice described
|
|
||||||
in Exhibit B to the Covered Software; or
|
|
||||||
|
|
||||||
(b) that the Covered Software was made available under the terms of
|
|
||||||
version 1.1 or earlier of the License, but not also under the
|
|
||||||
terms of a Secondary License.
|
|
||||||
|
|
||||||
1.6. "Executable Form"
|
|
||||||
means any form of the work other than Source Code Form.
|
|
||||||
|
|
||||||
1.7. "Larger Work"
|
|
||||||
means a work that combines Covered Software with other material, in
|
|
||||||
a separate file or files, that is not Covered Software.
|
|
||||||
|
|
||||||
1.8. "License"
|
|
||||||
means this document.
|
|
||||||
|
|
||||||
1.9. "Licensable"
|
|
||||||
means having the right to grant, to the maximum extent possible,
|
|
||||||
whether at the time of the initial grant or subsequently, any and
|
|
||||||
all of the rights conveyed by this License.
|
|
||||||
|
|
||||||
1.10. "Modifications"
|
|
||||||
means any of the following:
|
|
||||||
|
|
||||||
(a) any file in Source Code Form that results from an addition to,
|
|
||||||
deletion from, or modification of the contents of Covered
|
|
||||||
Software; or
|
|
||||||
|
|
||||||
(b) any new file in Source Code Form that contains any Covered
|
|
||||||
Software.
|
|
||||||
|
|
||||||
1.11. "Patent Claims" of a Contributor
|
|
||||||
means any patent claim(s), including without limitation, method,
|
|
||||||
process, and apparatus claims, in any patent Licensable by such
|
|
||||||
Contributor that would be infringed, but for the grant of the
|
|
||||||
License, by the making, using, selling, offering for sale, having
|
|
||||||
made, import, or transfer of either its Contributions or its
|
|
||||||
Contributor Version.
|
|
||||||
|
|
||||||
1.12. "Secondary License"
|
|
||||||
means either the GNU General Public License, Version 2.0, the GNU
|
|
||||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
|
||||||
Public License, Version 3.0, or any later versions of those
|
|
||||||
licenses.
|
|
||||||
|
|
||||||
1.13. "Source Code Form"
|
|
||||||
means the form of the work preferred for making modifications.
|
|
||||||
|
|
||||||
1.14. "You" (or "Your")
|
|
||||||
means an individual or a legal entity exercising rights under this
|
|
||||||
License. For legal entities, "You" includes any entity that
|
|
||||||
controls, is controlled by, or is under common control with You. For
|
|
||||||
purposes of this definition, "control" means (a) the power, direct
|
|
||||||
or indirect, to cause the direction or management of such entity,
|
|
||||||
whether by contract or otherwise, or (b) ownership of more than
|
|
||||||
fifty percent (50%) of the outstanding shares or beneficial
|
|
||||||
ownership of such entity.
|
|
||||||
|
|
||||||
2. License Grants and Conditions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
2.1. Grants
|
|
||||||
|
|
||||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
|
||||||
non-exclusive license:
|
|
||||||
|
|
||||||
(a) under intellectual property rights (other than patent or trademark)
|
|
||||||
Licensable by such Contributor to use, reproduce, make available,
|
|
||||||
modify, display, perform, distribute, and otherwise exploit its
|
|
||||||
Contributions, either on an unmodified basis, with Modifications, or
|
|
||||||
as part of a Larger Work; and
|
|
||||||
|
|
||||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
|
||||||
for sale, have made, import, and otherwise transfer either its
|
|
||||||
Contributions or its Contributor Version.
|
|
||||||
|
|
||||||
2.2. Effective Date
|
|
||||||
|
|
||||||
The licenses granted in Section 2.1 with respect to any Contribution
|
|
||||||
become effective for each Contribution on the date the Contributor first
|
|
||||||
distributes such Contribution.
|
|
||||||
|
|
||||||
2.3. Limitations on Grant Scope
|
|
||||||
|
|
||||||
The licenses granted in this Section 2 are the only rights granted under
|
|
||||||
this License. No additional rights or licenses will be implied from the
|
|
||||||
distribution or licensing of Covered Software under this License.
|
|
||||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
|
||||||
Contributor:
|
|
||||||
|
|
||||||
(a) for any code that a Contributor has removed from Covered Software;
|
|
||||||
or
|
|
||||||
|
|
||||||
(b) for infringements caused by: (i) Your and any other third party's
|
|
||||||
modifications of Covered Software, or (ii) the combination of its
|
|
||||||
Contributions with other software (except as part of its Contributor
|
|
||||||
Version); or
|
|
||||||
|
|
||||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
|
||||||
its Contributions.
|
|
||||||
|
|
||||||
This License does not grant any rights in the trademarks, service marks,
|
|
||||||
or logos of any Contributor (except as may be necessary to comply with
|
|
||||||
the notice requirements in Section 3.4).
|
|
||||||
|
|
||||||
2.4. Subsequent Licenses
|
|
||||||
|
|
||||||
No Contributor makes additional grants as a result of Your choice to
|
|
||||||
distribute the Covered Software under a subsequent version of this
|
|
||||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
|
||||||
permitted under the terms of Section 3.3).
|
|
||||||
|
|
||||||
2.5. Representation
|
|
||||||
|
|
||||||
Each Contributor represents that the Contributor believes its
|
|
||||||
Contributions are its original creation(s) or it has sufficient rights
|
|
||||||
to grant the rights to its Contributions conveyed by this License.
|
|
||||||
|
|
||||||
2.6. Fair Use
|
|
||||||
|
|
||||||
This License is not intended to limit any rights You have under
|
|
||||||
applicable copyright doctrines of fair use, fair dealing, or other
|
|
||||||
equivalents.
|
|
||||||
|
|
||||||
2.7. Conditions
|
|
||||||
|
|
||||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
|
||||||
in Section 2.1.
|
|
||||||
|
|
||||||
3. Responsibilities
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
3.1. Distribution of Source Form
|
|
||||||
|
|
||||||
All distribution of Covered Software in Source Code Form, including any
|
|
||||||
Modifications that You create or to which You contribute, must be under
|
|
||||||
the terms of this License. You must inform recipients that the Source
|
|
||||||
Code Form of the Covered Software is governed by the terms of this
|
|
||||||
License, and how they can obtain a copy of this License. You may not
|
|
||||||
attempt to alter or restrict the recipients' rights in the Source Code
|
|
||||||
Form.
|
|
||||||
|
|
||||||
3.2. Distribution of Executable Form
|
|
||||||
|
|
||||||
If You distribute Covered Software in Executable Form then:
|
|
||||||
|
|
||||||
(a) such Covered Software must also be made available in Source Code
|
|
||||||
Form, as described in Section 3.1, and You must inform recipients of
|
|
||||||
the Executable Form how they can obtain a copy of such Source Code
|
|
||||||
Form by reasonable means in a timely manner, at a charge no more
|
|
||||||
than the cost of distribution to the recipient; and
|
|
||||||
|
|
||||||
(b) You may distribute such Executable Form under the terms of this
|
|
||||||
License, or sublicense it under different terms, provided that the
|
|
||||||
license for the Executable Form does not attempt to limit or alter
|
|
||||||
the recipients' rights in the Source Code Form under this License.
|
|
||||||
|
|
||||||
3.3. Distribution of a Larger Work
|
|
||||||
|
|
||||||
You may create and distribute a Larger Work under terms of Your choice,
|
|
||||||
provided that You also comply with the requirements of this License for
|
|
||||||
the Covered Software. If the Larger Work is a combination of Covered
|
|
||||||
Software with a work governed by one or more Secondary Licenses, and the
|
|
||||||
Covered Software is not Incompatible With Secondary Licenses, this
|
|
||||||
License permits You to additionally distribute such Covered Software
|
|
||||||
under the terms of such Secondary License(s), so that the recipient of
|
|
||||||
the Larger Work may, at their option, further distribute the Covered
|
|
||||||
Software under the terms of either this License or such Secondary
|
|
||||||
License(s).
|
|
||||||
|
|
||||||
3.4. Notices
|
|
||||||
|
|
||||||
You may not remove or alter the substance of any license notices
|
|
||||||
(including copyright notices, patent notices, disclaimers of warranty,
|
|
||||||
or limitations of liability) contained within the Source Code Form of
|
|
||||||
the Covered Software, except that You may alter any license notices to
|
|
||||||
the extent required to remedy known factual inaccuracies.
|
|
||||||
|
|
||||||
3.5. Application of Additional Terms
|
|
||||||
|
|
||||||
You may choose to offer, and to charge a fee for, warranty, support,
|
|
||||||
indemnity or liability obligations to one or more recipients of Covered
|
|
||||||
Software. However, You may do so only on Your own behalf, and not on
|
|
||||||
behalf of any Contributor. You must make it absolutely clear that any
|
|
||||||
such warranty, support, indemnity, or liability obligation is offered by
|
|
||||||
You alone, and You hereby agree to indemnify every Contributor for any
|
|
||||||
liability incurred by such Contributor as a result of warranty, support,
|
|
||||||
indemnity or liability terms You offer. You may include additional
|
|
||||||
disclaimers of warranty and limitations of liability specific to any
|
|
||||||
jurisdiction.
|
|
||||||
|
|
||||||
4. Inability to Comply Due to Statute or Regulation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
If it is impossible for You to comply with any of the terms of this
|
|
||||||
License with respect to some or all of the Covered Software due to
|
|
||||||
statute, judicial order, or regulation then You must: (a) comply with
|
|
||||||
the terms of this License to the maximum extent possible; and (b)
|
|
||||||
describe the limitations and the code they affect. Such description must
|
|
||||||
be placed in a text file included with all distributions of the Covered
|
|
||||||
Software under this License. Except to the extent prohibited by statute
|
|
||||||
or regulation, such description must be sufficiently detailed for a
|
|
||||||
recipient of ordinary skill to be able to understand it.
|
|
||||||
|
|
||||||
5. Termination
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
5.1. The rights granted under this License will terminate automatically
|
|
||||||
if You fail to comply with any of its terms. However, if You become
|
|
||||||
compliant, then the rights granted under this License from a particular
|
|
||||||
Contributor are reinstated (a) provisionally, unless and until such
|
|
||||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
|
||||||
ongoing basis, if such Contributor fails to notify You of the
|
|
||||||
non-compliance by some reasonable means prior to 60 days after You have
|
|
||||||
come back into compliance. Moreover, Your grants from a particular
|
|
||||||
Contributor are reinstated on an ongoing basis if such Contributor
|
|
||||||
notifies You of the non-compliance by some reasonable means, this is the
|
|
||||||
first time You have received notice of non-compliance with this License
|
|
||||||
from such Contributor, and You become compliant prior to 30 days after
|
|
||||||
Your receipt of the notice.
|
|
||||||
|
|
||||||
5.2. If You initiate litigation against any entity by asserting a patent
|
|
||||||
infringement claim (excluding declaratory judgment actions,
|
|
||||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
|
||||||
directly or indirectly infringes any patent, then the rights granted to
|
|
||||||
You by any and all Contributors for the Covered Software under Section
|
|
||||||
2.1 of this License shall terminate.
|
|
||||||
|
|
||||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
|
||||||
end user license agreements (excluding distributors and resellers) which
|
|
||||||
have been validly granted by You or Your distributors under this License
|
|
||||||
prior to termination shall survive termination.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- *
|
|
||||||
- 6. Disclaimer of Warranty \*
|
|
||||||
- ------------------------- \*
|
|
||||||
- *
|
|
||||||
- Covered Software is provided under this License on an "as is" \*
|
|
||||||
- basis, without warranty of any kind, either expressed, implied, or \*
|
|
||||||
- statutory, including, without limitation, warranties that the \*
|
|
||||||
- Covered Software is free of defects, merchantable, fit for a \*
|
|
||||||
- particular purpose or non-infringing. The entire risk as to the \*
|
|
||||||
- quality and performance of the Covered Software is with You. \*
|
|
||||||
- Should any Covered Software prove defective in any respect, You \*
|
|
||||||
- (not any Contributor) assume the cost of any necessary servicing, \*
|
|
||||||
- repair, or correction. This disclaimer of warranty constitutes an \*
|
|
||||||
- essential part of this License. No use of any Covered Software is \*
|
|
||||||
- authorized under this License except under this disclaimer. \*
|
|
||||||
- *
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- *
|
|
||||||
- 7. Limitation of Liability \*
|
|
||||||
- -------------------------- \*
|
|
||||||
- *
|
|
||||||
- Under no circumstances and under no legal theory, whether tort \*
|
|
||||||
- (including negligence), contract, or otherwise, shall any \*
|
|
||||||
- Contributor, or anyone who distributes Covered Software as \*
|
|
||||||
- permitted above, be liable to You for any direct, indirect, \*
|
|
||||||
- special, incidental, or consequential damages of any character \*
|
|
||||||
- including, without limitation, damages for lost profits, loss of \*
|
|
||||||
- goodwill, work stoppage, computer failure or malfunction, or any \*
|
|
||||||
- and all other commercial damages or losses, even if such party \*
|
|
||||||
- shall have been informed of the possibility of such damages. This \*
|
|
||||||
- limitation of liability shall not apply to liability for death or \*
|
|
||||||
- personal injury resulting from such party's negligence to the \*
|
|
||||||
- extent applicable law prohibits such limitation. Some \*
|
|
||||||
- jurisdictions do not allow the exclusion or limitation of \*
|
|
||||||
- incidental or consequential damages, so this exclusion and \*
|
|
||||||
- limitation may not apply to You. \*
|
|
||||||
- *
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
8. Litigation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Any litigation relating to this License may be brought only in the
|
|
||||||
courts of a jurisdiction where the defendant maintains its principal
|
|
||||||
place of business and such litigation shall be governed by laws of that
|
|
||||||
jurisdiction, without reference to its conflict-of-law provisions.
|
|
||||||
Nothing in this Section shall prevent a party's ability to bring
|
|
||||||
cross-claims or counter-claims.
|
|
||||||
|
|
||||||
9. Miscellaneous
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
This License represents the complete agreement concerning the subject
|
|
||||||
matter hereof. If any provision of this License is held to be
|
|
||||||
unenforceable, such provision shall be reformed only to the extent
|
|
||||||
necessary to make it enforceable. Any law or regulation which provides
|
|
||||||
that the language of a contract shall be construed against the drafter
|
|
||||||
shall not be used to construe this License against a Contributor.
|
|
||||||
|
|
||||||
10. Versions of the License
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
10.1. New Versions
|
|
||||||
|
|
||||||
Mozilla Foundation is the license steward. Except as provided in Section
|
|
||||||
10.3, no one other than the license steward has the right to modify or
|
|
||||||
publish new versions of this License. Each version will be given a
|
|
||||||
distinguishing version number.
|
|
||||||
|
|
||||||
10.2. Effect of New Versions
|
|
||||||
|
|
||||||
You may distribute the Covered Software under the terms of the version
|
|
||||||
of the License under which You originally received the Covered Software,
|
|
||||||
or under the terms of any subsequent version published by the license
|
|
||||||
steward.
|
|
||||||
|
|
||||||
10.3. Modified Versions
|
|
||||||
|
|
||||||
If you create software not governed by this License, and you want to
|
|
||||||
create a new license for such software, you may create and use a
|
|
||||||
modified version of this License if you rename the license and remove
|
|
||||||
any references to the name of the license steward (except to note that
|
|
||||||
such modified license differs from this License).
|
|
||||||
|
|
||||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
|
||||||
Licenses
|
|
||||||
|
|
||||||
If You choose to distribute Source Code Form that is Incompatible With
|
|
||||||
Secondary Licenses under the terms of this version of the License, the
|
|
||||||
notice described in Exhibit B of this License must be attached.
|
|
||||||
|
|
||||||
## Exhibit A - Source Code Form License Notice
|
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
If it is not possible or desirable to put the notice in a particular
|
|
||||||
file, then You may include the notice in a location (such as a LICENSE
|
|
||||||
file in a relevant directory) where a recipient would be likely to look
|
|
||||||
for such a notice.
|
|
||||||
|
|
||||||
You may add additional accurate notices of copyright ownership.
|
|
||||||
|
|
||||||
## Exhibit B - "Incompatible With Secondary Licenses" Notice
|
|
||||||
|
|
||||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
||||||
defined by the Mozilla Public License, v. 2.0.
|
|
||||||
@@ -1,77 +1,86 @@
|
|||||||
## Quickstart
|
# Astroad
|
||||||
|
|
||||||
|
Astroad is a pre-configured setup for Astro and Payloadcms, designed to make it easy for you to start building your website. With Astroad, you'll have a complete development environment that you can run locally using Docker. This setup simplifies the testing and development of your website before deploying it to a production environment.
|
||||||
|
|
||||||
|
## Important notes
|
||||||
|
### Adding dependencies
|
||||||
|
1. When adding a dedpendency to astro or payload, you must regenerate the yarn.lock file for the dev server toinstall the dependencies.
|
||||||
|
2. Regenerate the lock file:
|
||||||
|
`cd astro` or `cd payload`
|
||||||
|
`rm yarn.lock`
|
||||||
|
`yarn install`
|
||||||
|
3. Restart dev server `yarn dev`
|
||||||
|
|
||||||
|
## Dev server getting stuck
|
||||||
|
Sometimes the dev script gets stuck, for an unknown reason:
|
||||||
```
|
```
|
||||||
docker-compose -f docker-compose.yml up
|
[+] Running 3/0
|
||||||
|
✔ Container astroad-mongo Running 0.0s
|
||||||
|
✔ Container astroad-payload Running 0.0s
|
||||||
|
✔ Container astroad-astro Running
|
||||||
```
|
```
|
||||||
|
|
||||||
## About
|
Running `yarn stop` and then `yarn dev` should resolve the issue.
|
||||||
This project was bootstrapped with a [turbopress](https://github.com/turbopress/turbopress)
|
|
||||||
|
|
||||||
### Useful docs
|
|
||||||
- [Astro docs: Integrating with PayloadCMS](https://docs.astro.build/en/guides/cms/payload/)
|
|
||||||
- [Astro docs: Deployment and SSR adapters](https://docs.astro.build/en/guides/deploy/)
|
|
||||||
- [Astro docs: On demand rendering adapters](https://docs.astro.build/en/guides/server-side-rendering/)
|
|
||||||
|
|
||||||
## Why Astro?
|
|
||||||
|
|
||||||
Astro allow you to use your favorite UI components and libraries. Mix and match React, Preact, Svelte, Vue, SolidJS, AlpineJS, and Lit to build your own website.
|
|
||||||
|
|
||||||
## Why PayloadCMS?
|
|
||||||
|
|
||||||
I need a headless CMS that is easy to use with TypeScript support. PayloadCMS work really well in this use case.
|
|
||||||
|
|
||||||
## What's inside?
|
|
||||||
|
|
||||||
This Turborepo includes the following packages/apps:
|
|
||||||
|
|
||||||
### Apps and Packages
|
|
||||||
|
|
||||||
- `api`: a [Payload](https://payloadcms.com/) app
|
|
||||||
- `web`: an [Astro](https://astro.build/) app
|
|
||||||
- `eslint-config-custom`: `eslint` configurations (includes `eslint-config-prettier` and `eslint-configg-turbo`)
|
|
||||||
|
|
||||||
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
|
|
||||||
|
|
||||||
### Utilities
|
|
||||||
|
|
||||||
This Turborepo has some additional tools already setup for you:
|
|
||||||
|
|
||||||
- [TypeScript](https://www.typescriptlang.org/) for static type checking
|
|
||||||
- [TailwindCSS](https://tailwindcss.com/) for CSS utility
|
|
||||||
- [ESLint](https://eslint.org/) for code linting
|
|
||||||
- [Prettier](https://prettier.io) for code formatting
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
Install `nodejs`, `pnpm` and `turborepo` on your local machine
|
Before getting started with Astroad, make sure you have the necessary software installed:
|
||||||
|
|
||||||
## Develop
|
- Docker
|
||||||
|
- Node.js
|
||||||
|
- Yarn
|
||||||
|
|
||||||
Create a `.env` file in the root folder, you can use the `.env.example` file as an example
|
## Configuration
|
||||||
|
|
||||||
I use `pnpm` for this project.
|
While there's no configuration necessary for local development, deployment via Github Workflows requires specific secrets and variables to be set.
|
||||||
|
|
||||||
Run the following command:
|
### Secrets:
|
||||||
|
|
||||||
```sh
|
- `USER`: User on the server
|
||||||
pnpm installl
|
- `HOST`: IP or URL of the server
|
||||||
pnpm dev
|
- `KEY`: SSH KEY for connecting to the server
|
||||||
```
|
- `MONGODB_PW`: Password for MongoDB
|
||||||
|
- `MONGODB_USER`: User for MongoDB
|
||||||
|
- `PATH`: Path where the repository resides on the server
|
||||||
|
- `PAYLOAD_PORT`: Port at which Payload listens
|
||||||
|
- `PAYLOAD_SECRET`: String to encrypt Payload data
|
||||||
|
- `TOKEN`: Github Access Token for the webhook to trigger the payload.yml workflow and execute a new Astro build
|
||||||
|
|
||||||
By default, the payloadCMS will run on port 3000, and Astro will be served on port 3001.
|
### Variables:
|
||||||
|
|
||||||
## Build & Serve (NodeJs)
|
- `ASTRO_HOST`: Hostdomain of the Frontend
|
||||||
|
- `PAYLOAD_HOST`: Hostdomain of the CMS
|
||||||
|
- `PAYLOAD_URL`: URL of the CMS
|
||||||
|
- `NAME`: Name of the Container and Project
|
||||||
|
|
||||||
```sh
|
Please remember to set these secrets and variables in your repository settings to ensure a successful deployment through Github Workflows.
|
||||||
pnpm build
|
|
||||||
pnpm serve
|
|
||||||
```
|
|
||||||
|
|
||||||
## TypeScript support
|
Once the secrets and variables are set on GitHub, they will replace the existing ones in the `.env` file on the server during deployment. This is done by the push.yml workflow, which replaces the placeholders in the `.env` with the actual secrets and variables defined in the repository settings. Please ensure that the names of your secrets and variables match with the placeholders in the `.env` file.
|
||||||
|
|
||||||
To fully utilize the type safe features, manually generate the types for PayloadCMS by runng `pnpm generate:types`
|
## Getting started
|
||||||
|
|
||||||
Then, you can import the types easily as simple as
|
To get started with Astroad, you'll need to have Docker and NPM || Yarn || PNPM installed on your machine.
|
||||||
|
|
||||||
```ts
|
You have two options for getting the repository:
|
||||||
import type { User } from "@turbopress/api/types";
|
|
||||||
```
|
1. Use the 'Use this template' button on the Github repository. This will create a new repository in your Github account with the same directory structure and files as Astroad. After the new repository is created, you can clone it to your local machine.
|
||||||
|
1. Alternatively, you can directly clone the Astroad repository: git clone https://github.com/mooxl/astroad.git. If you choose this option, remember to change the origin of your remote repository to a new one to avoid pushing changes directly to the Astroad repository. This can be done with the command: git remote set-url origin https://github.com/USERNAME/REPOSITORY.git where USERNAME is your username and REPOSITORY is the name of your new repository.
|
||||||
|
|
||||||
|
Once you've cloned the repository or created your own from the template, follow these steps:
|
||||||
|
|
||||||
|
1. Change into the repository directory: `cd {newName}`
|
||||||
|
1. Start the containers: `yarn dev`
|
||||||
|
|
||||||
|
This will start up the Astro, Payloadcms and Mongo containers and make them available on your local machine. Astro will be served at http://localhost:3000 and the Payload will be available at http://localhost:3001.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
The `docker-compose.yml` and `docker-compose-dev.yml` files includes everything you need to run the containers. The containers use the environment variables declared in the `.env` file and mounted volumes to store data persistently even after the containers are stopped and started.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Deployment is handled by a Github Actions Workflow on every push. It logs into the server via SSH, pulls or clones the latest version of the repository, and runs `yarn prod`.
|
||||||
|
|
||||||
|
Because Astro is completely static, a content change in the CMS must trigger a new build of Astro. Therefore, there’s a `payload.yml` workflow that gets triggered by a webhook after every content change from Payload.
|
||||||
|
|
||||||
|
Ensure you have Traefik set up as a reverse proxy before deployment. The prod script will launch your site in a production-ready environment.
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
**/node_modules
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
extends: ["custom"],
|
|
||||||
rules: {},
|
|
||||||
};
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
### Node ###
|
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
|
||||||
web_modules/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
.stylelintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
.env
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# vuepress v2.x temp and cache directory
|
|
||||||
.temp
|
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
|
||||||
.docusaurus
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
### Node Patch ###
|
|
||||||
# Serverless Webpack directories
|
|
||||||
.webpack/
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
|
|
||||||
# SvelteKit build / generate output
|
|
||||||
.svelte-kit
|
|
||||||
|
|
||||||
### VisualStudioCode ###
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
!.vscode/*.code-snippets
|
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
|
||||||
.history/
|
|
||||||
|
|
||||||
# Built Visual Studio Code Extensions
|
|
||||||
*.vsix
|
|
||||||
|
|
||||||
### VisualStudioCode Patch ###
|
|
||||||
# Ignore all local history of files
|
|
||||||
.history
|
|
||||||
.ionide
|
|
||||||
|
|
||||||
# Support for Project snippet scope
|
|
||||||
.vscode/*.code-snippets
|
|
||||||
|
|
||||||
# Ignore code-workspaces
|
|
||||||
*.code-workspace
|
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode
|
|
||||||
|
|
||||||
# Local media files
|
|
||||||
media
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
FROM node:18-alpine as base
|
|
||||||
RUN npm i -g pnpm turbo
|
|
||||||
|
|
||||||
FROM base AS pruner
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . .
|
|
||||||
RUN turbo prune --scope=@turbopress/api --docker
|
|
||||||
|
|
||||||
# remove all empty node_modules folder structure
|
|
||||||
RUN rm -rf /app/out/full/*/*/node_modules
|
|
||||||
|
|
||||||
FROM base AS builder
|
|
||||||
RUN apk add --no-cache libc6-compat
|
|
||||||
RUN apk update
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Add lockfile and package.json's of isolated subworkspace
|
|
||||||
COPY .gitignore .gitignore
|
|
||||||
COPY --from=pruner /app/out/json/ .
|
|
||||||
RUN pnpm install
|
|
||||||
|
|
||||||
# Build the project and its dependencies
|
|
||||||
COPY --from=pruner /app/out/full/ .
|
|
||||||
ENV PAYLOAD_CONFIG_PATH=src/payload.config.ts
|
|
||||||
RUN pnpm build:api
|
|
||||||
|
|
||||||
# Run App
|
|
||||||
FROM base as runner
|
|
||||||
WORKDIR /app
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
ENV PAYLOAD_CONFIG_PATH=dist/payload.config.js
|
|
||||||
COPY --from=builder /app/apps/api/package.json .
|
|
||||||
RUN pnpm install --prod
|
|
||||||
COPY --from=builder /app/apps/api/dist ./dist
|
|
||||||
COPY --from=builder /app/apps/api/build ./build
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
CMD ["node", "dist/server.js"]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# api
|
|
||||||
|
|
||||||
This project was created using create-payload-app using the blog template.
|
|
||||||
|
|
||||||
## How to Use
|
|
||||||
|
|
||||||
`yarn dev` will start up your application and reload on any changes.
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
If you have docker and docker-compose installed, you can run `docker-compose up`
|
|
||||||
|
|
||||||
To build the docker image, run `docker build -t my-tag -f Dockerfile ../..`
|
|
||||||
|
|
||||||
Ensure you are passing all needed environment variables when starting up your container via `--env-file` or setting them with your deployment.
|
|
||||||
|
|
||||||
The 3 typical env vars will be `MONGODB_URI`, `PAYLOAD_SECRET`, and `PAYLOAD_CONFIG_PATH`
|
|
||||||
|
|
||||||
`docker run --env-file .env -p 3000:3000 my-tag`
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"watch": ["src"],
|
|
||||||
"ext": "ts",
|
|
||||||
"exec": "ts-node src/server.ts"
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@turbopress/api",
|
|
||||||
"description": "Headless CMS based on Payload",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "dist/server.js",
|
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "nodemon",
|
|
||||||
"build:payload": "payload build",
|
|
||||||
"build:server": "tsc",
|
|
||||||
"build": "pnpm copyfiles && pnpm build:payload && pnpm build:server",
|
|
||||||
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
|
|
||||||
"generate:types": "payload generate:types",
|
|
||||||
"lint": "eslint \"./src/**/*.{js,ts}\" --fix",
|
|
||||||
"serve": "PAYLOAD_CONFIG_PATH=dist/payload.config.js node dist/server.js"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"dotenv": "^8.2.0",
|
|
||||||
"express": "^4.17.1",
|
|
||||||
"payload": "1.12.0",
|
|
||||||
"@payloadcms/plugin-seo": "latest",
|
|
||||||
"@payloadcms/plugin-cloud-storage": "latest",
|
|
||||||
"@aws-sdk/client-s3": "latest",
|
|
||||||
"@aws-sdk/lib-storage": "latest"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"eslint": "latest",
|
|
||||||
"@types/express": "^4.17.9",
|
|
||||||
"copyfiles": "^2.4.1",
|
|
||||||
"nodemon": "^2.0.6",
|
|
||||||
"ts-node": "^9.1.1",
|
|
||||||
"typescript": "^4.8.4",
|
|
||||||
"webpack-hot-middleware": "^2.25.4",
|
|
||||||
"eslint-config-custom": "*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
import payload from "payload";
|
|
||||||
import { Block } from "payload/types";
|
|
||||||
import Pages from "../collections/Pages";
|
|
||||||
import linkField from "../fields/linkField";
|
|
||||||
|
|
||||||
export const Menu: Block = {
|
|
||||||
slug: "menu",
|
|
||||||
interfaceName: "Menu",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "type",
|
|
||||||
type: "select",
|
|
||||||
options: ["default"],
|
|
||||||
required: true,
|
|
||||||
defaultValue: "default",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "menus",
|
|
||||||
type: "array",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "mainMenu",
|
|
||||||
type: "group",
|
|
||||||
interfaceName: "MainMenu",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
type: "row",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "type",
|
|
||||||
type: "radio",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: "Internal link",
|
|
||||||
value: "reference",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Custom URL",
|
|
||||||
value: "custom",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "None",
|
|
||||||
value: "none",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
defaultValue: "reference",
|
|
||||||
admin: {
|
|
||||||
layout: "horizontal",
|
|
||||||
width: "50%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "newTab",
|
|
||||||
label: "Open in new tab",
|
|
||||||
type: "checkbox",
|
|
||||||
admin: {
|
|
||||||
condition: (_, siblingData) => siblingData?.type != "none",
|
|
||||||
width: "50%",
|
|
||||||
style: {
|
|
||||||
alignSelf: "flex-end",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "reference",
|
|
||||||
label: "Document to link to",
|
|
||||||
type: "relationship",
|
|
||||||
relationTo: [Pages.slug],
|
|
||||||
required: true,
|
|
||||||
maxDepth: 0,
|
|
||||||
admin: {
|
|
||||||
condition: (_, siblingData) =>
|
|
||||||
siblingData?.type === "reference",
|
|
||||||
width: "50%",
|
|
||||||
},
|
|
||||||
hooks: {
|
|
||||||
afterRead: [
|
|
||||||
async ({ value, siblingData }) => {
|
|
||||||
if (value && siblingData.type === "reference") {
|
|
||||||
const id = value.value;
|
|
||||||
const pages = await payload.find({
|
|
||||||
collection: "pages",
|
|
||||||
where: {
|
|
||||||
id: { equals: id },
|
|
||||||
},
|
|
||||||
depth: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pages.docs[0]?.slug)
|
|
||||||
siblingData.url = pages.docs[0].slug;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "url",
|
|
||||||
label: "Custom URL",
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
admin: {
|
|
||||||
condition: (_, siblingData) =>
|
|
||||||
siblingData?.type === "custom",
|
|
||||||
width: "50%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "label",
|
|
||||||
label: "Label",
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
admin: {
|
|
||||||
width: "50%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "subMenu",
|
|
||||||
type: "array",
|
|
||||||
fields: [linkField()],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Block } from "payload/types";
|
|
||||||
|
|
||||||
export const PageContent: Block = {
|
|
||||||
slug: "pageContent",
|
|
||||||
interfaceName: "PageContent",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "description",
|
|
||||||
type: "textarea",
|
|
||||||
defaultValue:
|
|
||||||
"This block will display the content of the page (if any). Please edit the original page change the value.",
|
|
||||||
admin: {
|
|
||||||
readOnly: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { Block } from "payload/types";
|
|
||||||
import Categories from "../collections/Categories";
|
|
||||||
import { PagesField } from "../collections/Pages";
|
|
||||||
import Tags from "../collections/Tags";
|
|
||||||
|
|
||||||
const PageListField = {
|
|
||||||
numberOfItems: "numberOfItems",
|
|
||||||
filterByCategories: "filterByCategories",
|
|
||||||
filterByTags: "filterByTags",
|
|
||||||
sortBy: "sortBy",
|
|
||||||
pages: "pages",
|
|
||||||
};
|
|
||||||
|
|
||||||
type PageListField = (typeof PageListField)[keyof typeof PageListField];
|
|
||||||
|
|
||||||
export const PageList: Block = {
|
|
||||||
slug: "pageList",
|
|
||||||
interfaceName: "PageList",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: PageListField.numberOfItems,
|
|
||||||
type: "number",
|
|
||||||
defaultValue: 5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: PageListField.filterByCategories,
|
|
||||||
type: "relationship",
|
|
||||||
relationTo: [Categories.slug],
|
|
||||||
maxDepth: 0,
|
|
||||||
hasMany: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: PageListField.filterByTags,
|
|
||||||
type: "relationship",
|
|
||||||
relationTo: [Tags.slug],
|
|
||||||
hasMany: true,
|
|
||||||
maxDepth: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: PageListField.sortBy,
|
|
||||||
type: "select",
|
|
||||||
options: [
|
|
||||||
PagesField.title,
|
|
||||||
PagesField.createdAt,
|
|
||||||
PagesField.updatedAt,
|
|
||||||
`-${PagesField.title}`,
|
|
||||||
`-${PagesField.createdAt}`,
|
|
||||||
`-${PagesField.updatedAt}`,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Block } from "payload/types";
|
|
||||||
import Contents from "../collections/Contents";
|
|
||||||
|
|
||||||
export const ReusableContent: Block = {
|
|
||||||
slug: "reusableContent",
|
|
||||||
interfaceName: "ReusableContent",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "reference",
|
|
||||||
type: "relationship",
|
|
||||||
// maxDepth: 0,
|
|
||||||
relationTo: [Contents.slug],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { Block } from "payload/types";
|
|
||||||
|
|
||||||
export const SiteTitle: Block = {
|
|
||||||
slug: "siteTitle",
|
|
||||||
interfaceName: "SiteTitle",
|
|
||||||
fields: [
|
|
||||||
{ name: "siteName", type: "text", required: true, admin: { width: "50%" } },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import type { CollectionConfig } from "payload/types";
|
|
||||||
|
|
||||||
const CategoriesField = {
|
|
||||||
name: "name",
|
|
||||||
slug: "slug",
|
|
||||||
};
|
|
||||||
|
|
||||||
type CategoriesField = (typeof CategoriesField)[keyof typeof CategoriesField];
|
|
||||||
|
|
||||||
const Categories: CollectionConfig = {
|
|
||||||
slug: "categories",
|
|
||||||
admin: {
|
|
||||||
useAsTitle: CategoriesField.name,
|
|
||||||
},
|
|
||||||
access: {
|
|
||||||
read: () => true,
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: CategoriesField.name,
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: CategoriesField.slug,
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
unique: true,
|
|
||||||
admin: {
|
|
||||||
position: "sidebar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
timestamps: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Categories;
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { CollectionConfig } from "payload/types";
|
|
||||||
import { Menu } from "../blocks/Menu";
|
|
||||||
import { PageContent } from "../blocks/PageContent";
|
|
||||||
import { PageList } from "../blocks/PageList";
|
|
||||||
import { SiteTitle } from "../blocks/SiteTitle";
|
|
||||||
|
|
||||||
const ContentsField = {
|
|
||||||
name: "name",
|
|
||||||
slug: "slug",
|
|
||||||
description: "description",
|
|
||||||
};
|
|
||||||
type ContentsField = (typeof ContentsField)[keyof typeof ContentsField];
|
|
||||||
|
|
||||||
const Contents: CollectionConfig = {
|
|
||||||
slug: "contents",
|
|
||||||
access: {
|
|
||||||
read: () => true,
|
|
||||||
},
|
|
||||||
admin: {
|
|
||||||
useAsTitle: ContentsField.name,
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: ContentsField.name,
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ContentsField.slug,
|
|
||||||
type: "text",
|
|
||||||
unique: true,
|
|
||||||
admin: {
|
|
||||||
position: "sidebar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ContentsField.description,
|
|
||||||
type: "text",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "blocks",
|
|
||||||
type: "blocks",
|
|
||||||
blocks: [Menu, PageContent, PageList, SiteTitle],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Contents;
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import { CollectionConfig } from "payload/types";
|
|
||||||
import { Menu } from "../blocks/Menu";
|
|
||||||
import { PageContent } from "../blocks/PageContent";
|
|
||||||
import { PageList } from "../blocks/PageList";
|
|
||||||
import { ReusableContent } from "../blocks/ReusableContent";
|
|
||||||
import { SiteTitle } from "../blocks/SiteTitle";
|
|
||||||
|
|
||||||
const LayoutsField = {
|
|
||||||
name: "name",
|
|
||||||
slug: "slug",
|
|
||||||
description: "description",
|
|
||||||
};
|
|
||||||
type LayoutsField = (typeof LayoutsField)[keyof typeof LayoutsField];
|
|
||||||
|
|
||||||
const blocks = [ReusableContent];
|
|
||||||
|
|
||||||
const Layouts: CollectionConfig = {
|
|
||||||
slug: "layouts",
|
|
||||||
access: {
|
|
||||||
read: () => true,
|
|
||||||
},
|
|
||||||
admin: {
|
|
||||||
useAsTitle: LayoutsField.name,
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: LayoutsField.name,
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: LayoutsField.slug,
|
|
||||||
type: "text",
|
|
||||||
unique: true,
|
|
||||||
admin: {
|
|
||||||
position: "sidebar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: LayoutsField.description,
|
|
||||||
type: "text",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "header",
|
|
||||||
type: "group",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "blocks",
|
|
||||||
type: "blocks",
|
|
||||||
blocks: [...blocks, Menu, SiteTitle],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "body",
|
|
||||||
type: "group",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "blocks",
|
|
||||||
type: "blocks",
|
|
||||||
blocks: [...blocks, PageContent, PageList],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "footer",
|
|
||||||
type: "group",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "blocks",
|
|
||||||
type: "blocks",
|
|
||||||
blocks: blocks,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Layouts;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import type { CollectionConfig } from "payload/types";
|
|
||||||
|
|
||||||
const Media: CollectionConfig = {
|
|
||||||
slug: "media",
|
|
||||||
access: {
|
|
||||||
read: () => true,
|
|
||||||
},
|
|
||||||
upload: {
|
|
||||||
disableLocalStorage: true,
|
|
||||||
adminThumbnail: "thumbnail",
|
|
||||||
imageSizes: [
|
|
||||||
{
|
|
||||||
height: 400,
|
|
||||||
width: 400,
|
|
||||||
crop: "center",
|
|
||||||
name: "thumbnail",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
width: 900,
|
|
||||||
height: 450,
|
|
||||||
crop: "center",
|
|
||||||
name: "sixteenByNineMedium",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
fields: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Media;
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import type { CollectionConfig } from "payload/types";
|
|
||||||
|
|
||||||
export const PagesField = {
|
|
||||||
title: "title",
|
|
||||||
slug: "slug",
|
|
||||||
author: "author",
|
|
||||||
publishedDate: "publishedDate",
|
|
||||||
categories: "categories",
|
|
||||||
tags: "tags",
|
|
||||||
content: "content",
|
|
||||||
status: "status",
|
|
||||||
layout: "layout",
|
|
||||||
createdAt: "createdAt",
|
|
||||||
updatedAt: "updatedAt",
|
|
||||||
};
|
|
||||||
|
|
||||||
type PagesField = (typeof PagesField)[keyof typeof PagesField];
|
|
||||||
|
|
||||||
const PagesFieldStatus = {
|
|
||||||
Draft: "Draft",
|
|
||||||
Published: "Published",
|
|
||||||
};
|
|
||||||
|
|
||||||
type PagesFieldStatus =
|
|
||||||
(typeof PagesFieldStatus)[keyof typeof PagesFieldStatus];
|
|
||||||
|
|
||||||
const Pages: CollectionConfig = {
|
|
||||||
slug: "pages",
|
|
||||||
admin: {
|
|
||||||
defaultColumns: [
|
|
||||||
PagesField.title,
|
|
||||||
PagesField.slug,
|
|
||||||
PagesField.author,
|
|
||||||
PagesField.categories,
|
|
||||||
PagesField.tags,
|
|
||||||
PagesField.status,
|
|
||||||
],
|
|
||||||
useAsTitle: PagesField.title,
|
|
||||||
},
|
|
||||||
access: {
|
|
||||||
read: () => true,
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: PagesField.title,
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: PagesField.slug,
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
admin: {
|
|
||||||
position: "sidebar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: PagesField.author,
|
|
||||||
type: "relationship",
|
|
||||||
relationTo: "users",
|
|
||||||
admin: {
|
|
||||||
position: "sidebar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: PagesField.publishedDate,
|
|
||||||
type: "date",
|
|
||||||
admin: {
|
|
||||||
position: "sidebar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: PagesField.categories,
|
|
||||||
type: "relationship",
|
|
||||||
relationTo: "categories",
|
|
||||||
hasMany: true,
|
|
||||||
admin: {
|
|
||||||
position: "sidebar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: PagesField.tags,
|
|
||||||
type: "relationship",
|
|
||||||
relationTo: "tags",
|
|
||||||
hasMany: true,
|
|
||||||
admin: {
|
|
||||||
position: "sidebar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: PagesField.status,
|
|
||||||
type: "select",
|
|
||||||
options: Object.entries(PagesFieldStatus).map((e) => {
|
|
||||||
return { label: e[0], value: e[1] };
|
|
||||||
}),
|
|
||||||
defaultValue: PagesFieldStatus.Draft,
|
|
||||||
admin: {
|
|
||||||
position: "sidebar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: PagesField.layout,
|
|
||||||
type: "relationship",
|
|
||||||
relationTo: "layouts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: PagesField.content,
|
|
||||||
type: "richText",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Pages;
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import type { CollectionConfig } from "payload/types";
|
|
||||||
|
|
||||||
const TagsField = {
|
|
||||||
name: "name",
|
|
||||||
slug: "slug",
|
|
||||||
};
|
|
||||||
|
|
||||||
type TagsField = (typeof TagsField)[keyof typeof TagsField];
|
|
||||||
|
|
||||||
const Tags: CollectionConfig = {
|
|
||||||
slug: "tags",
|
|
||||||
admin: {
|
|
||||||
useAsTitle: TagsField.name,
|
|
||||||
},
|
|
||||||
access: {
|
|
||||||
read: () => true,
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: TagsField.name,
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: TagsField.slug,
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
unique: true,
|
|
||||||
admin: {
|
|
||||||
position: "sidebar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
timestamps: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Tags;
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import type { CollectionConfig } from "payload/types";
|
|
||||||
|
|
||||||
const Users: CollectionConfig = {
|
|
||||||
slug: "users",
|
|
||||||
auth: true,
|
|
||||||
admin: {
|
|
||||||
useAsTitle: "email",
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
// Email added by default
|
|
||||||
{
|
|
||||||
name: "name",
|
|
||||||
type: "text",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Users;
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { SelectField } from "payload/types";
|
|
||||||
|
|
||||||
function enableField(fieldOverrides?: Partial<SelectField>): SelectField {
|
|
||||||
return {
|
|
||||||
label: "Enable",
|
|
||||||
name: "enable",
|
|
||||||
type: "select",
|
|
||||||
options: ["Yes", "No"],
|
|
||||||
defaultValue: "Yes",
|
|
||||||
required: true,
|
|
||||||
...fieldOverrides,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default enableField;
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import payload from "payload";
|
|
||||||
import { GroupField } from "payload/types";
|
|
||||||
|
|
||||||
function linkField(fieldOverrides?: Partial<GroupField>): GroupField {
|
|
||||||
return {
|
|
||||||
name: "link",
|
|
||||||
type: "group",
|
|
||||||
interfaceName: "Link",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
type: "row",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "type",
|
|
||||||
type: "radio",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: "Internal link",
|
|
||||||
value: "reference",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Custom URL",
|
|
||||||
value: "custom",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
defaultValue: "reference",
|
|
||||||
admin: {
|
|
||||||
layout: "horizontal",
|
|
||||||
width: "50%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "newTab",
|
|
||||||
label: "Open in new tab",
|
|
||||||
type: "checkbox",
|
|
||||||
admin: {
|
|
||||||
width: "50%",
|
|
||||||
style: {
|
|
||||||
alignSelf: "flex-end",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "reference",
|
|
||||||
label: "Document to link to",
|
|
||||||
type: "relationship",
|
|
||||||
relationTo: ["pages"],
|
|
||||||
required: true,
|
|
||||||
maxDepth: 0,
|
|
||||||
admin: {
|
|
||||||
condition: (_, siblingData) => siblingData?.type === "reference",
|
|
||||||
width: "50%",
|
|
||||||
},
|
|
||||||
hooks: {
|
|
||||||
afterRead: [
|
|
||||||
async ({ value, siblingData }) => {
|
|
||||||
if (value && siblingData.type === "reference") {
|
|
||||||
const id = value.value;
|
|
||||||
const pages = await payload.find({
|
|
||||||
collection: "pages",
|
|
||||||
where: {
|
|
||||||
id: { equals: id },
|
|
||||||
},
|
|
||||||
depth: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pages.docs[0]?.slug)
|
|
||||||
if (pages.docs[0]) siblingData.url = pages.docs[0].slug;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "url",
|
|
||||||
label: "Custom URL",
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
admin: {
|
|
||||||
condition: (_, siblingData) => siblingData?.type === "custom",
|
|
||||||
width: "50%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "label",
|
|
||||||
label: "Label",
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
admin: {
|
|
||||||
width: "50%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
...fieldOverrides,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default linkField;
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import { cloudStorage } from "@payloadcms/plugin-cloud-storage";
|
|
||||||
import { s3Adapter } from "@payloadcms/plugin-cloud-storage/s3";
|
|
||||||
import seo from "@payloadcms/plugin-seo";
|
|
||||||
import { GenerateTitle } from "@payloadcms/plugin-seo/dist/types";
|
|
||||||
import path from "path";
|
|
||||||
import { buildConfig } from "payload/config";
|
|
||||||
import Categories from "./collections/Categories";
|
|
||||||
import Contents from "./collections/Contents";
|
|
||||||
import Layouts from "./collections/Layouts";
|
|
||||||
import Media from "./collections/Media";
|
|
||||||
import Pages from "./collections/Pages";
|
|
||||||
import Tags from "./collections/Tags";
|
|
||||||
import Users from "./collections/Users";
|
|
||||||
const generateTitle: GenerateTitle = ({ slug, doc }) => {
|
|
||||||
let title = "TurboPress";
|
|
||||||
if (slug == "pages") {
|
|
||||||
const page = doc as any;
|
|
||||||
return (title = `TurboPress - ${page?.title?.value}`);
|
|
||||||
}
|
|
||||||
return title;
|
|
||||||
};
|
|
||||||
|
|
||||||
const adapter = s3Adapter({
|
|
||||||
config: {
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
|
||||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
|
||||||
},
|
|
||||||
region: process.env.S3_REGION,
|
|
||||||
endpoint: process.env.S3_ENDPOINT,
|
|
||||||
},
|
|
||||||
bucket: process.env.S3_BUCKET,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default buildConfig({
|
|
||||||
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL ?? "http://localhost:3000",
|
|
||||||
admin: {
|
|
||||||
user: Users.slug,
|
|
||||||
},
|
|
||||||
collections: [Categories, Contents, Layouts, Media, Pages, Tags, Users],
|
|
||||||
typescript: {
|
|
||||||
outputFile: path.join(__dirname, "../types", "payload.ts"),
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
seo({
|
|
||||||
collections: ["pages"],
|
|
||||||
uploadsCollection: "media",
|
|
||||||
generateTitle: generateTitle,
|
|
||||||
}),
|
|
||||||
cloudStorage({
|
|
||||||
collections: {
|
|
||||||
media: {
|
|
||||||
adapter: adapter,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
cors: "*",
|
|
||||||
});
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import express from "express";
|
|
||||||
import payload from "payload";
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
// Redirect root to Admin panel
|
|
||||||
app.get("/", (_, res) => {
|
|
||||||
res.redirect("/admin");
|
|
||||||
});
|
|
||||||
|
|
||||||
const PORT = Number(process.env.PAYLOAD_PORT) ?? 3000;
|
|
||||||
const HOST = process.env.PAYLOAD_HOST ?? 'localhost';
|
|
||||||
|
|
||||||
const start = async () => {
|
|
||||||
// Initialize Payload
|
|
||||||
await payload.init({
|
|
||||||
secret: process.env.PAYLOAD_SECRET,
|
|
||||||
mongoURL: process.env.MONGODB_URI,
|
|
||||||
express: app,
|
|
||||||
onInit: async () => {
|
|
||||||
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
|
|
||||||
},
|
|
||||||
mongoOptions: {
|
|
||||||
dbName: process.env.DB_NAME,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add your own express routes here
|
|
||||||
|
|
||||||
app.listen(
|
|
||||||
PORT,
|
|
||||||
HOST,
|
|
||||||
() => {
|
|
||||||
console.log(`Server running at http://${HOST}:${PORT}/`);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
start();
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from "./payload";
|
|
||||||
export * from "./rich-text-export";
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* This file was automatically generated by Payload.
|
|
||||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
|
||||||
* and re-run `payload generate:types` to regenerate this file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface Config {
|
|
||||||
collections: {
|
|
||||||
categories: Category;
|
|
||||||
contents: Content;
|
|
||||||
layouts: Layout;
|
|
||||||
media: Media;
|
|
||||||
pages: Page;
|
|
||||||
tags: Tag;
|
|
||||||
users: User;
|
|
||||||
};
|
|
||||||
globals: {};
|
|
||||||
}
|
|
||||||
export interface Category {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
}
|
|
||||||
export interface Content {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug?: string;
|
|
||||||
description?: string;
|
|
||||||
blocks?: (Menu | PageContent | PageList | SiteTitle)[];
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
export interface Menu {
|
|
||||||
type: 'default';
|
|
||||||
menus?: {
|
|
||||||
mainMenu: MainMenu;
|
|
||||||
id?: string;
|
|
||||||
}[];
|
|
||||||
id?: string;
|
|
||||||
blockName?: string;
|
|
||||||
blockType: 'menu';
|
|
||||||
}
|
|
||||||
export interface MainMenu {
|
|
||||||
type?: 'reference' | 'custom' | 'none';
|
|
||||||
newTab?: boolean;
|
|
||||||
reference: {
|
|
||||||
value: string | Page;
|
|
||||||
relationTo: 'pages';
|
|
||||||
};
|
|
||||||
url: string;
|
|
||||||
label: string;
|
|
||||||
subMenu?: {
|
|
||||||
link: Link;
|
|
||||||
id?: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
export interface Page {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
slug: string;
|
|
||||||
author?: string | User;
|
|
||||||
publishedDate?: string;
|
|
||||||
categories?: string[] | Category[];
|
|
||||||
tags?: string[] | Tag[];
|
|
||||||
status?: 'Draft' | 'Published';
|
|
||||||
layout?: string | Layout;
|
|
||||||
content?: {
|
|
||||||
[k: string]: unknown;
|
|
||||||
}[];
|
|
||||||
meta?: {
|
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
image?: string | Media;
|
|
||||||
};
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
export interface User {
|
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
email: string;
|
|
||||||
resetPasswordToken?: string;
|
|
||||||
resetPasswordExpiration?: string;
|
|
||||||
salt?: string;
|
|
||||||
hash?: string;
|
|
||||||
loginAttempts?: number;
|
|
||||||
lockUntil?: string;
|
|
||||||
password?: string;
|
|
||||||
}
|
|
||||||
export interface Tag {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
}
|
|
||||||
export interface Layout {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug?: string;
|
|
||||||
description?: string;
|
|
||||||
header?: {
|
|
||||||
blocks?: (ReusableContent | Menu | SiteTitle)[];
|
|
||||||
};
|
|
||||||
body?: {
|
|
||||||
blocks?: (ReusableContent | PageContent | PageList)[];
|
|
||||||
};
|
|
||||||
footer?: {
|
|
||||||
blocks?: ReusableContent[];
|
|
||||||
};
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
export interface ReusableContent {
|
|
||||||
reference?: {
|
|
||||||
value: string | Content;
|
|
||||||
relationTo: 'contents';
|
|
||||||
};
|
|
||||||
id?: string;
|
|
||||||
blockName?: string;
|
|
||||||
blockType: 'reusableContent';
|
|
||||||
}
|
|
||||||
export interface SiteTitle {
|
|
||||||
siteName: string;
|
|
||||||
id?: string;
|
|
||||||
blockName?: string;
|
|
||||||
blockType: 'siteTitle';
|
|
||||||
}
|
|
||||||
export interface PageContent {
|
|
||||||
description?: string;
|
|
||||||
id?: string;
|
|
||||||
blockName?: string;
|
|
||||||
blockType: 'pageContent';
|
|
||||||
}
|
|
||||||
export interface PageList {
|
|
||||||
numberOfItems?: number;
|
|
||||||
filterByCategories?:
|
|
||||||
| {
|
|
||||||
value: string;
|
|
||||||
relationTo: 'categories';
|
|
||||||
}[]
|
|
||||||
| {
|
|
||||||
value: Category;
|
|
||||||
relationTo: 'categories';
|
|
||||||
}[];
|
|
||||||
filterByTags?:
|
|
||||||
| {
|
|
||||||
value: string;
|
|
||||||
relationTo: 'tags';
|
|
||||||
}[]
|
|
||||||
| {
|
|
||||||
value: Tag;
|
|
||||||
relationTo: 'tags';
|
|
||||||
}[];
|
|
||||||
sortBy?: 'title' | 'createdAt' | 'updatedAt' | '-title' | '-createdAt' | '-updatedAt';
|
|
||||||
id?: string;
|
|
||||||
blockName?: string;
|
|
||||||
blockType: 'pageList';
|
|
||||||
}
|
|
||||||
export interface Media {
|
|
||||||
id: string;
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
url?: string;
|
|
||||||
filename?: string;
|
|
||||||
mimeType?: string;
|
|
||||||
filesize?: number;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
sizes?: {
|
|
||||||
thumbnail?: {
|
|
||||||
url?: string;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
mimeType?: string;
|
|
||||||
filesize?: number;
|
|
||||||
filename?: string;
|
|
||||||
};
|
|
||||||
sixteenByNineMedium?: {
|
|
||||||
url?: string;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
mimeType?: string;
|
|
||||||
filesize?: number;
|
|
||||||
filename?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export interface Link {
|
|
||||||
type?: 'reference' | 'custom';
|
|
||||||
newTab?: boolean;
|
|
||||||
reference: {
|
|
||||||
value: string | Page;
|
|
||||||
relationTo: 'pages';
|
|
||||||
};
|
|
||||||
url: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import type {
|
|
||||||
RichTextElement,
|
|
||||||
RichTextLeaf,
|
|
||||||
} from "payload/dist/fields/config/types";
|
|
||||||
import type { RichTextCustomElement, RichTextCustomLeaf } from "payload/types";
|
|
||||||
|
|
||||||
type DefaultRichTextLeaf = Exclude<RichTextLeaf, RichTextCustomLeaf>;
|
|
||||||
export type FormattedText = {
|
|
||||||
[key in DefaultRichTextLeaf]?: boolean;
|
|
||||||
} & {
|
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DefaultRichTextElement =
|
|
||||||
| Exclude<RichTextElement, RichTextCustomElement>
|
|
||||||
| "li"
|
|
||||||
| "quote";
|
|
||||||
export type FormattedElement = {
|
|
||||||
type: DefaultRichTextElement;
|
|
||||||
url?: string;
|
|
||||||
children: FormattedText[];
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
extends: ["custom", "plugin:astro/recommended"],
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ["*.astro"],
|
|
||||||
parser: "astro-eslint-parser",
|
|
||||||
parserOptions: {
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
extraFileExtensions: [".astro"],
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Astro with Tailwind
|
|
||||||
|
|
||||||
```
|
|
||||||
npm create astro@latest -- --template with-tailwindcss
|
|
||||||
```
|
|
||||||
|
|
||||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/with-tailwindcss)
|
|
||||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/with-tailwindcss)
|
|
||||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/with-tailwindcss/devcontainer.json)
|
|
||||||
|
|
||||||
Astro comes with [Tailwind](https://tailwindcss.com) support out of the box. This example showcases how to style your Astro project with Tailwind.
|
|
||||||
|
|
||||||
For complete setup instructions, please see our [Tailwind Integration Guide](https://docs.astro.build/en/guides/integrations-guide/tailwind).
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import mdx from "@astrojs/mdx";
|
|
||||||
import node from "@astrojs/node";
|
|
||||||
import svelte from "@astrojs/svelte";
|
|
||||||
import tailwind from "@astrojs/tailwind";
|
|
||||||
|
|
||||||
import { defineConfig } from "astro/config";
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
integrations: [mdx(), tailwind(), svelte()],
|
|
||||||
server: {
|
|
||||||
port: parseInt(process.env.ASTRO_PORT ?? "3000"),
|
|
||||||
// FIXME 3wc: seems to be ignored?
|
|
||||||
hostname: process.env.ASTRO_HOST ?? "0.0.0.0",
|
|
||||||
},
|
|
||||||
output: "server",
|
|
||||||
adapter: node({
|
|
||||||
mode: "standalone",
|
|
||||||
}),
|
|
||||||
vite: {
|
|
||||||
// FIXME 3wc: shouldn't need to hardcode this
|
|
||||||
define: {
|
|
||||||
"import.meta.env.PAYLOAD_PUBLIC_SERVER_URL": JSON.stringify(
|
|
||||||
"http://api:3001"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@turbopress/web",
|
|
||||||
"description": "Front end website based on Astro",
|
|
||||||
"type": "module",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "astro dev",
|
|
||||||
"start": "astro dev",
|
|
||||||
"build": "astro build",
|
|
||||||
"preview": "astro preview",
|
|
||||||
"astro": "astro",
|
|
||||||
"lint": "eslint \"./src/**/*.{js,ts,tsx,astro}\" --fix",
|
|
||||||
"serve": "node dist/server/entry.mjs"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/mdx": "latest",
|
|
||||||
"@astrojs/node": "latest",
|
|
||||||
"@astrojs/svelte": "latest",
|
|
||||||
"@astrojs/tailwind": "latest",
|
|
||||||
"@turbopress/api": "*",
|
|
||||||
"astro": "latest",
|
|
||||||
"autoprefixer": "latest",
|
|
||||||
"@iconify/svelte": "latest",
|
|
||||||
"postcss": "latest",
|
|
||||||
"qs": "^6.11.2",
|
|
||||||
"slate": "latest",
|
|
||||||
"tailwindcss": "latest",
|
|
||||||
"astro-icon": "latest",
|
|
||||||
"nanostores": "latest",
|
|
||||||
"svelte": "latest",
|
|
||||||
"svelte-inview": "4.0.1 "
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
|
||||||
"@types/escape-html": "^1.0.2",
|
|
||||||
"@types/qs": "^6.9.7",
|
|
||||||
"escape-html": "^1.0.3",
|
|
||||||
"eslint": "latest",
|
|
||||||
"eslint-config-custom": "*",
|
|
||||||
"eslint-plugin-astro": "latest",
|
|
||||||
"svelte-breakpoints": "latest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<div class="grid place-items-center h-full w-full content-center">
|
|
||||||
<div class="text-xl font-bold">No homepage has been setup.</div>
|
|
||||||
<div class="text-lg">
|
|
||||||
Create a new page with slug 'home' in the admin panel.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
import type {
|
|
||||||
FormattedElement,
|
|
||||||
FormattedText,
|
|
||||||
Layout,
|
|
||||||
} from "@turbopress/api/types";
|
|
||||||
import RenderBody from "./body/RenderBody.astro";
|
|
||||||
import RenderHeader from "./header/RenderHeader.astro";
|
|
||||||
interface Props {
|
|
||||||
layout: Layout;
|
|
||||||
content?: (FormattedElement | FormattedText)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { layout, content } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<RenderHeader blocks={layout.header?.blocks} />
|
|
||||||
<RenderBody blocks={layout.body?.blocks} {content} />
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
import type { Content } from "@turbopress/api/types";
|
|
||||||
import { getContentSingle } from "../services/api/content.service";
|
|
||||||
import RenderSiteTitle from "./header/RenderSiteTitle.astro";
|
|
||||||
import RenderMenu from "./menu/RenderMenu.astro";
|
|
||||||
interface Props {
|
|
||||||
content: string | Content;
|
|
||||||
}
|
|
||||||
|
|
||||||
const content: Content | undefined =
|
|
||||||
typeof Astro.props.content == "string"
|
|
||||||
? await getContentSingle(Astro.props.content)
|
|
||||||
: Astro.props.content;
|
|
||||||
|
|
||||||
if (!content?.blocks) return;
|
|
||||||
|
|
||||||
const blocks = content.blocks;
|
|
||||||
---
|
|
||||||
|
|
||||||
{
|
|
||||||
blocks.map((block) => {
|
|
||||||
if (!block.id) return;
|
|
||||||
if (block.blockType == "siteTitle")
|
|
||||||
return <RenderSiteTitle siteTitle={block} />;
|
|
||||||
if (block.blockType == "menu" && block.menus)
|
|
||||||
return <RenderMenu menu={block} />;
|
|
||||||
|
|
||||||
return <div>block = {block.id}</div>;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
import type {
|
|
||||||
FormattedElement,
|
|
||||||
FormattedText,
|
|
||||||
PageContent,
|
|
||||||
PageList,
|
|
||||||
ReusableContent,
|
|
||||||
} from "@turbopress/api/types";
|
|
||||||
import RenderContent from "../RenderReusableContent.astro";
|
|
||||||
import RenderPageContent from "./RenderPageContent.astro";
|
|
||||||
// import RenderPageList from "./page-list/RenderPageList.astro";
|
|
||||||
import RenderPageList from "./page-list/RenderPageList.svelte";
|
|
||||||
interface Props {
|
|
||||||
blocks?: (PageList | ReusableContent | PageContent)[];
|
|
||||||
content?: (FormattedElement | FormattedText)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { blocks = [], content } = Astro.props;
|
|
||||||
|
|
||||||
if (blocks.length === 0) return;
|
|
||||||
---
|
|
||||||
|
|
||||||
<main class="p-6 flex flex-wrap">
|
|
||||||
{
|
|
||||||
blocks.map((block) => {
|
|
||||||
if (!block.id) return;
|
|
||||||
if (block.blockType == "reusableContent" && block.reference?.value)
|
|
||||||
return <RenderContent content={block.reference.value} />;
|
|
||||||
if (block.blockType == "pageContent")
|
|
||||||
return <RenderPageContent {content} />;
|
|
||||||
if (block.blockType == "pageList")
|
|
||||||
return <RenderPageList {block} client:only />;
|
|
||||||
return <div>{block.id}</div>;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</main>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
import type { FormattedElement, FormattedText } from "@turbopress/api/types";
|
|
||||||
import RichText from "../rich-text/RichText.astro";
|
|
||||||
interface Props {
|
|
||||||
content?: (FormattedElement | FormattedText)[];
|
|
||||||
title?: string;
|
|
||||||
}
|
|
||||||
const { content } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
{
|
|
||||||
content && (
|
|
||||||
<article class="w-full justify-center prose max-w-none prose-headings:m-0 prose-headings:my-3 prose-p:m-0 prose-p:my-2">
|
|
||||||
<RichText richText={content} />
|
|
||||||
</article>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Page } from "@turbopress/api/types";
|
|
||||||
import RichText from "../../rich-text/RichText.svelte";
|
|
||||||
|
|
||||||
export let page: Page;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if page.content}
|
|
||||||
<section class="">
|
|
||||||
<header>
|
|
||||||
<h2>{page.title}</h2>
|
|
||||||
</header>
|
|
||||||
<article>
|
|
||||||
<RichText richText={page.content}></RichText>
|
|
||||||
</article>
|
|
||||||
<hr />
|
|
||||||
</section>
|
|
||||||
{/if}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Page, PageList } from "@turbopress/api/types";
|
|
||||||
import { inview } from "svelte-inview";
|
|
||||||
import { writable, type Writable } from "svelte/store";
|
|
||||||
import { getPageCollection } from "../../../services/api";
|
|
||||||
import type { PayloadCollection } from "../../../types";
|
|
||||||
import PageListItem from "./PageListItem.svelte";
|
|
||||||
|
|
||||||
export let block: PageList;
|
|
||||||
|
|
||||||
const query = {
|
|
||||||
where: {
|
|
||||||
or: [
|
|
||||||
{
|
|
||||||
categories: {
|
|
||||||
in: block.filterByCategories?.map((f) => f.value),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tags: {
|
|
||||||
in: block.filterByTags?.map((f) => f.value),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
limit: block.numberOfItems ?? 5,
|
|
||||||
page: 1,
|
|
||||||
sort: block.sortBy,
|
|
||||||
};
|
|
||||||
|
|
||||||
const queryState = writable(query);
|
|
||||||
const collection: Writable<PayloadCollection<Page> | undefined> = writable();
|
|
||||||
|
|
||||||
async function getPages() {
|
|
||||||
const pages = await getPageCollection($queryState);
|
|
||||||
collection.set(pages);
|
|
||||||
}
|
|
||||||
|
|
||||||
queryState.subscribe((s) => {
|
|
||||||
getPages();
|
|
||||||
});
|
|
||||||
|
|
||||||
$: pages = $collection?.docs;
|
|
||||||
|
|
||||||
function handleChange({ detail }: CustomEvent<ObserverEventDetails>) {
|
|
||||||
isInView = detail.inView;
|
|
||||||
if (detail.inView && $collection?.hasNextPage) {
|
|
||||||
queryState.set({ ...$queryState, limit: $queryState.limit + 1 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let isInView: boolean;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="w-full prose max-w-none prose-headings:m-0 prose-headings:my-3 prose-p:m-0 prose-p:my-2"
|
|
||||||
>
|
|
||||||
{#if pages}
|
|
||||||
{#each pages as page}
|
|
||||||
<PageListItem {page}></PageListItem>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div use:inview on:inview_change={handleChange}></div>
|
|
||||||
</div>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
import type { Menu, ReusableContent, SiteTitle } from "@turbopress/api/types";
|
|
||||||
import RenderContent from "../RenderReusableContent.astro";
|
|
||||||
import RenderMenu from "../menu/RenderMenu.astro";
|
|
||||||
import RenderSiteTitle from "./RenderSiteTitle.astro";
|
|
||||||
// import SiteTitle from "./SiteTitle.astro";
|
|
||||||
interface Props {
|
|
||||||
blocks?: (Menu | ReusableContent | SiteTitle)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { blocks = [] } = Astro.props;
|
|
||||||
|
|
||||||
if (blocks.length === 0) return;
|
|
||||||
---
|
|
||||||
|
|
||||||
<header class="shadow p-6 flex flex-wrap">
|
|
||||||
{
|
|
||||||
blocks.map((block) => {
|
|
||||||
if (!block.id) return;
|
|
||||||
if (block.blockType == "siteTitle")
|
|
||||||
return <RenderSiteTitle siteTitle={block} />;
|
|
||||||
if (block.blockType == "reusableContent" && block.reference?.value)
|
|
||||||
return <RenderContent content={block.reference.value} />;
|
|
||||||
if (block.blockType == "menu" && block.menus)
|
|
||||||
return <RenderMenu menu={block} />;
|
|
||||||
return <div>{block.id}</div>;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</header>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
import type { SiteTitle } from "@turbopress/api/types";
|
|
||||||
import Link from "../link/Link.astro";
|
|
||||||
interface Props {
|
|
||||||
siteTitle: SiteTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { siteTitle } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<div class="flex-grow">
|
|
||||||
<Link link="/">
|
|
||||||
<div class="font-bold text-lg">{siteTitle.siteName}</div>
|
|
||||||
</Link>
|
|
||||||
<div class="w-full"></div>
|
|
||||||
</div>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
interface Props {
|
|
||||||
link?: string | undefined;
|
|
||||||
target?: "_self" | "_blank" | "_top" | "_parent";
|
|
||||||
class?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { link, target, class: className } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
{
|
|
||||||
link && (
|
|
||||||
<a
|
|
||||||
href={link}
|
|
||||||
{target}
|
|
||||||
class={"cursor-pointer hover:text-indigo-600 " + className}
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{!link && <slot />}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let link: string | undefined = undefined;
|
|
||||||
export let target:
|
|
||||||
| "_self"
|
|
||||||
| "_blank"
|
|
||||||
| "_top"
|
|
||||||
| "_parent"
|
|
||||||
| undefined
|
|
||||||
| null = undefined;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
{#if link}
|
|
||||||
<a
|
|
||||||
href={link}
|
|
||||||
{target}
|
|
||||||
class="cursor-pointer hover:text-indigo-600 {$$props.class ?? ''}"
|
|
||||||
><slot /></a
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<slot />
|
|
||||||
{/if}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
import type { MainMenu, Menu } from "@turbopress/api/types";
|
|
||||||
import DefaultMenu from "./default/DefaultMenu.svelte";
|
|
||||||
interface Props {
|
|
||||||
menu: Menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { menu } = Astro.props;
|
|
||||||
const menus = menu.menus ?? [];
|
|
||||||
|
|
||||||
const mainMenus: MainMenu[] = menus.map((menu) => menu.mainMenu);
|
|
||||||
---
|
|
||||||
|
|
||||||
{
|
|
||||||
() => {
|
|
||||||
return <DefaultMenu menus={mainMenus} client:only="svelte" />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { MainMenu } from "@turbopress/api/types";
|
|
||||||
import { useMediaQuery } from "svelte-breakpoints";
|
|
||||||
import DefaultDesktopMenu from "./desktop/DefaultDesktopMenu.svelte";
|
|
||||||
import DefaultMobileMenu from "./mobile/DefaultMobileMenu.svelte";
|
|
||||||
|
|
||||||
export let menus: MainMenu[];
|
|
||||||
|
|
||||||
const isDesktop = useMediaQuery("(min-width: 1024px)");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if !$isDesktop}
|
|
||||||
<DefaultMobileMenu {menus}></DefaultMobileMenu>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if $isDesktop}
|
|
||||||
<DefaultDesktopMenu {menus}></DefaultDesktopMenu>
|
|
||||||
{/if}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { map } from "nanostores";
|
|
||||||
|
|
||||||
interface MobileMenuState {
|
|
||||||
isOpen: boolean;
|
|
||||||
activeIndex?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const mobileMenuState = map<MobileMenuState>({
|
|
||||||
isOpen: false,
|
|
||||||
activeIndex: undefined,
|
|
||||||
});
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { MainMenu } from "@turbopress/api/types";
|
|
||||||
import { mobileMenuState } from "../defaultMenu";
|
|
||||||
import MainMenuSvelte from "./_MainMenu.svelte";
|
|
||||||
|
|
||||||
export let menus: MainMenu[];
|
|
||||||
|
|
||||||
function handleClick() {
|
|
||||||
mobileMenuState.setKey("isOpen", !isOpen);
|
|
||||||
}
|
|
||||||
|
|
||||||
$: isOpen = $mobileMenuState.isOpen;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<div
|
|
||||||
class="flex items-center cursor-pointer text-sm"
|
|
||||||
on:click={handleClick}
|
|
||||||
on:keypress={handleClick}
|
|
||||||
>
|
|
||||||
{#each menus as menu, i}
|
|
||||||
<MainMenuSvelte {menu} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- {#if isOpen}
|
|
||||||
<div class="w-full cursor-pointer lg:hidden">
|
|
||||||
{#each menus as menu, i}
|
|
||||||
<MainMenuSvelte {menu} index={i} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if} -->
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { MainMenu } from "@turbopress/api/types";
|
|
||||||
import Link from "../../../link/Link.svelte";
|
|
||||||
|
|
||||||
export let menu: MainMenu;
|
|
||||||
|
|
||||||
const subMenus = menu.subMenu ?? [];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="group relative inline-block text-left group">
|
|
||||||
<div id="menu-button" aria-expanded="false" aria-haspopup="true">
|
|
||||||
<Link
|
|
||||||
link={menu.url}
|
|
||||||
class="hover:text-black w-full group-hover:bg-slate-100 p-2 "
|
|
||||||
>
|
|
||||||
{menu.label}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="mt-2 absolute right-0 z-10 w-56 origin-top-right rounded-sm bg-white ring-1 ring-slate-200 focus:outline-none hidden group-hover:block"
|
|
||||||
role="menu"
|
|
||||||
aria-orientation="vertical"
|
|
||||||
aria-labelledby="menu-button"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div class="" role="none">
|
|
||||||
{#each subMenus as subMenu}
|
|
||||||
<Link link={subMenu.link.url}>
|
|
||||||
<div
|
|
||||||
class="px-3 py-1.5 block hover:bg-slate-100 ring-1 ring-inset ring-gray-200 ring-opacity-30"
|
|
||||||
>
|
|
||||||
{subMenu.link.label}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Link } from "@turbopress/api/types";
|
|
||||||
import LinkSvelte from "../../../link/Link.svelte";
|
|
||||||
export let subMenu: {
|
|
||||||
link: Link;
|
|
||||||
id?: string;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<LinkSvelte
|
|
||||||
link={subMenu.link.url}
|
|
||||||
class="h-7 hover:text-black hover:bg-slate-100 flex items-center"
|
|
||||||
>
|
|
||||||
<div class="w-full px-3">
|
|
||||||
{subMenu.link.label}
|
|
||||||
</div>
|
|
||||||
</LinkSvelte>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Icon from "@iconify/svelte";
|
|
||||||
import type { MainMenu } from "@turbopress/api/types";
|
|
||||||
import { mobileMenuState } from "../defaultMenu";
|
|
||||||
import MainMenuSvelte from "./_MainMenu.svelte";
|
|
||||||
|
|
||||||
export let menus: MainMenu[];
|
|
||||||
|
|
||||||
function handleClick() {
|
|
||||||
mobileMenuState.setKey("isOpen", !isOpen);
|
|
||||||
}
|
|
||||||
|
|
||||||
$: isOpen = $mobileMenuState.isOpen;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<div
|
|
||||||
class="flex items-center cursor-pointer font-semibold hover:text-indigo-600"
|
|
||||||
on:click={handleClick}
|
|
||||||
on:keypress={handleClick}
|
|
||||||
>
|
|
||||||
{#if isOpen}
|
|
||||||
<Icon icon="ic:round-close" class="h-5 w-5 mr-2" />
|
|
||||||
{:else}
|
|
||||||
<Icon icon="ic:baseline-menu" class="h-5 w-5 mr-2" />
|
|
||||||
{/if}
|
|
||||||
<div>Menu</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if isOpen}
|
|
||||||
<div class="w-full cursor-pointer lg:hidden">
|
|
||||||
{#each menus as menu, i}
|
|
||||||
<MainMenuSvelte {menu} index={i} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Icon from "@iconify/svelte";
|
|
||||||
import type { MainMenu } from "@turbopress/api/types";
|
|
||||||
import Link from "../../../link/Link.svelte";
|
|
||||||
import { mobileMenuState } from "../defaultMenu";
|
|
||||||
import SubMenu from "./_SubMenu.svelte";
|
|
||||||
|
|
||||||
export let menu: MainMenu;
|
|
||||||
export let index: number;
|
|
||||||
|
|
||||||
$: isOpen = $mobileMenuState.activeIndex == index;
|
|
||||||
|
|
||||||
function handleClick() {
|
|
||||||
if (isOpen) mobileMenuState.setKey("activeIndex", undefined);
|
|
||||||
else mobileMenuState.setKey("activeIndex", index);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex items-center h-7">
|
|
||||||
<Link link={menu.url} class="hover:text-black w-full hover:bg-slate-100 p-1 ">
|
|
||||||
<div class="w-full">{menu.label}</div>
|
|
||||||
</Link>
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<div
|
|
||||||
class="hover:bg-slate-100 h-8 w-10 {isOpen ? 'bg-indigo-50' : ''}"
|
|
||||||
on:click={handleClick}
|
|
||||||
on:keydown={handleClick}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon="ic:sharp-keyboard-arrow-down"
|
|
||||||
class="text-2xl mx-auto mt-1 transition-transform duration-200 {isOpen
|
|
||||||
? 'rotate-180 text-indigo-800 '
|
|
||||||
: ''}"
|
|
||||||
></Icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if isOpen}
|
|
||||||
{#if menu.subMenu}
|
|
||||||
{#each menu.subMenu as subMenu}
|
|
||||||
<SubMenu {subMenu} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Link } from "@turbopress/api/types";
|
|
||||||
import LinkSvelte from "../../../link/Link.svelte";
|
|
||||||
export let subMenu: {
|
|
||||||
link: Link;
|
|
||||||
id?: string;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<LinkSvelte
|
|
||||||
link={subMenu.link.url}
|
|
||||||
class="h-7 hover:text-black hover:bg-slate-100 flex items-center"
|
|
||||||
>
|
|
||||||
<div class="w-full px-3">
|
|
||||||
{subMenu.link.label}
|
|
||||||
</div>
|
|
||||||
</LinkSvelte>
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
---
|
|
||||||
import type { FormattedElement, FormattedText } from "@turbopress/api/types";
|
|
||||||
import escapeHTML from "escape-html";
|
|
||||||
import { Text } from "slate";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
richText: (FormattedElement | FormattedText)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { richText } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
{
|
|
||||||
richText.map((node) => {
|
|
||||||
return Text.isText(node) ? (
|
|
||||||
<Fragment>
|
|
||||||
{node.bold && <strong>{node.text}</strong>}
|
|
||||||
{node.code && <code>{node.text}</code>}
|
|
||||||
{node.italic && <em>{node.text}</em>}
|
|
||||||
{!node.bold && !node.code && !node.italic && (
|
|
||||||
<Fragment>{node.text}</Fragment>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
) : (
|
|
||||||
<Fragment>
|
|
||||||
{node.type === "h1" && (
|
|
||||||
<h1>{<Astro.self richText={node.children} />}</h1>
|
|
||||||
)}
|
|
||||||
{node.type === "h2" && (
|
|
||||||
<h2>{<Astro.self richText={node.children} />}</h2>
|
|
||||||
)}
|
|
||||||
{node.type === "h3" && (
|
|
||||||
<h3>{<Astro.self richText={node.children} />}</h3>
|
|
||||||
)}
|
|
||||||
{node.type === "h4" && (
|
|
||||||
<h4>{<Astro.self richText={node.children} />}</h4>
|
|
||||||
)}
|
|
||||||
{node.type === "h5" && (
|
|
||||||
<h5>{<Astro.self richText={node.children} />}</h5>
|
|
||||||
)}
|
|
||||||
{node.type === "h6" && (
|
|
||||||
<h6>{<Astro.self richText={node.children} />}</h6>
|
|
||||||
)}
|
|
||||||
{node.type === "quote" && (
|
|
||||||
<p>{<Astro.self richText={node.children} />}</p>
|
|
||||||
)}
|
|
||||||
{node.type === "ul" && (
|
|
||||||
<ul>{<Astro.self richText={node.children} />}</ul>
|
|
||||||
)}
|
|
||||||
{node.type === "ol" && (
|
|
||||||
<ol>{<Astro.self richText={node.children} />}</ol>
|
|
||||||
)}
|
|
||||||
{node.type === "li" && (
|
|
||||||
<li>{<Astro.self richText={node.children} />}</li>
|
|
||||||
)}
|
|
||||||
{node.type === "link" && (
|
|
||||||
<a href={escapeHTML(node.url)}>
|
|
||||||
{<Astro.self richText={node.children} />}
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
{!node.type && <p>{<Astro.self richText={node.children} />}</p>}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { FormattedElement, FormattedText } from "@turbopress/api/types";
|
|
||||||
import { Text } from "slate";
|
|
||||||
|
|
||||||
export let richText: (FormattedElement | FormattedText | any)[];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#each richText as node}
|
|
||||||
{#if Text.isText(node)}
|
|
||||||
{#if node.bold}
|
|
||||||
<strong>{node.text}</strong>
|
|
||||||
{/if}
|
|
||||||
{#if node.code}
|
|
||||||
<strong>{node.text}</strong>
|
|
||||||
{/if}
|
|
||||||
{#if node.italic}
|
|
||||||
<strong>{node.text}</strong>
|
|
||||||
{/if}
|
|
||||||
{#if !node.bold && !node.code && !node.italic}
|
|
||||||
{node.text}
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
{#if node.type === "h1"}
|
|
||||||
<h1><svelte:self richText={node.children}></svelte:self></h1>
|
|
||||||
{/if}
|
|
||||||
{#if node.type === "h2"}
|
|
||||||
<h2><svelte:self richText={node.children}></svelte:self></h2>
|
|
||||||
{/if}
|
|
||||||
{#if node.type === "h3"}
|
|
||||||
<h3><svelte:self richText={node.children}></svelte:self></h3>
|
|
||||||
{/if}
|
|
||||||
{#if node.type === "h4"}
|
|
||||||
<h4><svelte:self richText={node.children}></svelte:self></h4>
|
|
||||||
{/if}
|
|
||||||
{#if node.type === "h5"}
|
|
||||||
<h5><svelte:self richText={node.children}></svelte:self></h5>
|
|
||||||
{/if}
|
|
||||||
{#if node.type === "h6"}
|
|
||||||
<h6><svelte:self richText={node.children}></svelte:self></h6>
|
|
||||||
{/if}
|
|
||||||
{#if !node.type}
|
|
||||||
<p><svelte:self richText={node.children}></svelte:self></p>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
Vendored
-5
@@ -1,5 +0,0 @@
|
|||||||
/// <reference types="astro/client" />
|
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
|
||||||
readonly ASTRO_PORT: string;
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
---
|
|
||||||
import type {
|
|
||||||
FormattedElement,
|
|
||||||
FormattedText,
|
|
||||||
Layout,
|
|
||||||
Media,
|
|
||||||
} from "@turbopress/api/types";
|
|
||||||
import RenderLayout from "../components/RenderLayout.astro";
|
|
||||||
interface Props {
|
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
layout?: string | Layout;
|
|
||||||
content?: (FormattedElement | FormattedText | any)[];
|
|
||||||
image?: string | Media;
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
title = "AstroCMS",
|
|
||||||
description = "Astro, TailwindCSS, and PayloadCMS",
|
|
||||||
layout,
|
|
||||||
content,
|
|
||||||
image,
|
|
||||||
} = Astro.props;
|
|
||||||
|
|
||||||
const metaImage = image
|
|
||||||
? typeof image === "string"
|
|
||||||
? image
|
|
||||||
: image.url
|
|
||||||
: undefined;
|
|
||||||
---
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width" />
|
|
||||||
<meta name="description" content={description} />
|
|
||||||
{metaImage && <meta property="og:image" content={metaImage} />}
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
||||||
<title>{title}</title>
|
|
||||||
</head>
|
|
||||||
<body class="h-screen">
|
|
||||||
{
|
|
||||||
layout && typeof layout != "string" && (
|
|
||||||
<RenderLayout layout={layout} {content} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{!layout && <slot />}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
import MainLayout from "../layouts/MainLayout.astro";
|
|
||||||
import { getLayoutSingle } from "../services/api/layout.service";
|
|
||||||
|
|
||||||
const layout = await getLayoutSingle("404");
|
|
||||||
---
|
|
||||||
|
|
||||||
<MainLayout title="Error 404" layout={layout} description="Page not found">
|
|
||||||
Not found, error 404 The page you are looking for no longer exists. Perhaps
|
|
||||||
you can return back to the homepage and see if you can find what you are
|
|
||||||
looking for. Or, you can try finding it by using the search form below.
|
|
||||||
</MainLayout>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
import MainLayout from "../layouts/MainLayout.astro";
|
|
||||||
import { getPageSingle } from "../services/api";
|
|
||||||
const { slug } = Astro.params;
|
|
||||||
|
|
||||||
const page = await getPageSingle(slug!);
|
|
||||||
if (!page) return Astro.redirect("/404");
|
|
||||||
if (page.slug == "home") return Astro.redirect("/");
|
|
||||||
---
|
|
||||||
|
|
||||||
<MainLayout title={page.title} layout={page.layout} description="">
|
|
||||||
{page.title}
|
|
||||||
{page.layout}
|
|
||||||
<!-- {homePage && <RenderPage page={homePage} />}
|
|
||||||
{!homePage && <NoHomePage />} -->
|
|
||||||
</MainLayout>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
import MainLayout from "../layouts/MainLayout.astro";
|
|
||||||
import { getPageSingle } from "../services/api";
|
|
||||||
|
|
||||||
const homePage = await getPageSingle("home");
|
|
||||||
|
|
||||||
const pageTitle = homePage?.meta?.title ?? homePage?.title ?? "TurboPress";
|
|
||||||
|
|
||||||
const layout = homePage?.layout;
|
|
||||||
---
|
|
||||||
|
|
||||||
<MainLayout
|
|
||||||
title={pageTitle}
|
|
||||||
{layout}
|
|
||||||
description={homePage?.meta?.description}
|
|
||||||
content={homePage?.content}
|
|
||||||
image={homePage?.meta?.image}
|
|
||||||
/>
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import qs from "qs";
|
|
||||||
import type { PayloadCollection } from "../../types";
|
|
||||||
|
|
||||||
export async function apiFetch<T = any>(
|
|
||||||
url: string | URL,
|
|
||||||
options: RequestInit = {},
|
|
||||||
) {
|
|
||||||
const defaultOptions = {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await fetch(url, { ...defaultOptions, ...options });
|
|
||||||
if (res.ok) {
|
|
||||||
return res.json() as T;
|
|
||||||
}
|
|
||||||
throw new Error(`Error fetching data: ${res.statusText} (${res.status})}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPayloadCollection<CollectionType>(
|
|
||||||
url: string | URL,
|
|
||||||
query: any = null,
|
|
||||||
) {
|
|
||||||
const stringifiedQuery = qs.stringify(query, { addQueryPrefix: true });
|
|
||||||
return apiFetch<PayloadCollection<CollectionType>>(url + stringifiedQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPayloadDocument<CollectionType>(url: string | URL) {
|
|
||||||
return apiFetch<CollectionType>(url);
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import type { Layout } from "@turbopress/api/types";
|
|
||||||
import { getPayloadCollection } from "./api.service";
|
|
||||||
|
|
||||||
export async function getContentCollection(query: any = null) {
|
|
||||||
const url = ` ${process.env.PAYLOAD_PUBLIC_SERVER_URL}/api/layouts`;
|
|
||||||
return getPayloadCollection<Layout>(url, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getContentSingle(
|
|
||||||
name: string,
|
|
||||||
): Promise<Layout | undefined> {
|
|
||||||
const pages = await getContentCollection({
|
|
||||||
where: { name: { equals: name } },
|
|
||||||
});
|
|
||||||
if (pages.docs[0]) return pages.docs[0];
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from "./api.service";
|
|
||||||
export * from "./page.service";
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import type { Layout } from "@turbopress/api/types";
|
|
||||||
import { getPayloadCollection } from "./api.service";
|
|
||||||
|
|
||||||
export async function getLayoutCollection(query: any = null) {
|
|
||||||
const url = ` ${process.env.PAYLOAD_PUBLIC_SERVER_URL}/api/layouts`;
|
|
||||||
return getPayloadCollection<Layout>(url, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getLayoutSingle(
|
|
||||||
name: string,
|
|
||||||
): Promise<Layout | undefined> {
|
|
||||||
const pages = await getLayoutCollection({
|
|
||||||
where: { name: { equals: name } },
|
|
||||||
});
|
|
||||||
if (pages.docs[0]) return pages.docs[0];
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import type { Media } from "@turbopress/api/types";
|
|
||||||
import { getPayloadCollection, getPayloadDocument } from "./api.service";
|
|
||||||
|
|
||||||
export async function getMediaCollection(query: any = null) {
|
|
||||||
const url = `${process.env.PAYLOAD_PUBLIC_SERVER_URL}/api/medias`;
|
|
||||||
return getPayloadCollection<Media>(url, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMediaSingle(slug: string): Promise<Media | undefined> {
|
|
||||||
const medias = await getMediaCollection({
|
|
||||||
where: { slug: { equals: slug } },
|
|
||||||
});
|
|
||||||
if (medias.docs[0]) return medias.docs[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMediaById(id: string): Promise<Media | undefined> {
|
|
||||||
const url = `${process.env.PAYLOAD_PUBLIC_SERVER_URL}/api/media/` + id;
|
|
||||||
return getPayloadDocument<Media>(url);
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import type { Page } from "@turbopress/api/types";
|
|
||||||
import { getPayloadCollection } from "./api.service";
|
|
||||||
|
|
||||||
export async function getPageCollection(query: any = null) {
|
|
||||||
// FIXME 3wc: shouldn't need to hardcode this?!
|
|
||||||
//const url = `${import.meta.env.PAYLOAD_PUBLIC_SERVER_URL}/api/pages`;
|
|
||||||
const url = `http://api:3001/api/pages`;
|
|
||||||
return getPayloadCollection<Page>(url, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPageSingle(slug: string): Promise<Page | undefined> {
|
|
||||||
const pages = await getPageCollection({
|
|
||||||
where: { slug: { equals: slug } },
|
|
||||||
});
|
|
||||||
if (pages.docs[0]) return pages.docs[0];
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import type { FormattedElement, FormattedText } from "@turbopress/api/types";
|
|
||||||
|
|
||||||
declare module "slate" {
|
|
||||||
interface CustomTypes {
|
|
||||||
Element: FormattedElement;
|
|
||||||
Text: FormattedText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PayloadCollection<CollectionType = any> = {
|
|
||||||
totalDocs?: number;
|
|
||||||
limit?: number;
|
|
||||||
totalPages?: number;
|
|
||||||
page?: number;
|
|
||||||
pagingCounter?: number;
|
|
||||||
hasPrevPage?: boolean;
|
|
||||||
hasNextPage?: boolean;
|
|
||||||
prevPage?: number;
|
|
||||||
nextPage?: number;
|
|
||||||
hasMore?: boolean;
|
|
||||||
docs: CollectionType[];
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [require("@tailwindcss/typography")],
|
|
||||||
};
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "astro/tsconfigs/strict",
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
Dockerfile
|
||||||
|
README.md
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
PAYLOAD_URL=http://localhost:3001
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# build output
|
# build output
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
# generated types
|
# generated types
|
||||||
.astro/
|
.astro/
|
||||||
|
|
||||||
@@ -13,9 +14,8 @@ yarn-error.log*
|
|||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
# environment variables
|
|
||||||
.env
|
|
||||||
.env.production
|
|
||||||
|
|
||||||
# macOS-specific files
|
# macOS-specific files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# types
|
||||||
|
src/types.ts
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-astro", "prettier-plugin-tailwindcss"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.astro",
|
||||||
|
"options": {
|
||||||
|
"parser": "astro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
FROM node:lts-alpine as base
|
||||||
|
WORKDIR /base
|
||||||
|
COPY package.json yarn.lock ./
|
||||||
|
RUN yarn install --frozen-lockfile
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
FROM base AS dev
|
||||||
|
ENV NODE_ENV=development
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["yarn","dev"]
|
||||||
|
|
||||||
|
FROM base AS build
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
WORKDIR /build
|
||||||
|
COPY --from=base /base .
|
||||||
|
ADD "https://random-uuid.deno.dev" skipcache
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
FROM nginx:stable AS prod
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY --from=build /build/dist /usr/share/nginx/html
|
||||||
|
EXPOSE 3000
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { defineConfig } from "astro/config";
|
||||||
|
import tailwind from "@astrojs/tailwind";
|
||||||
|
import image from "@astrojs/image";
|
||||||
|
import sitemap from "@astrojs/sitemap";
|
||||||
|
import prefetch from "@astrojs/prefetch";
|
||||||
|
import react from "@astrojs/react";
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
compressHTML: true,
|
||||||
|
server: { port: 3000 },
|
||||||
|
build: {
|
||||||
|
inlineStylesheets: "auto"
|
||||||
|
},
|
||||||
|
viewTransitions: true,
|
||||||
|
integrations: [tailwind({
|
||||||
|
config: {
|
||||||
|
applyBaseStyles: false
|
||||||
|
}
|
||||||
|
}), image({
|
||||||
|
serviceEntryPoint: "@astrojs/image/sharp"
|
||||||
|
}), prefetch({
|
||||||
|
selector: "a"
|
||||||
|
}), sitemap(), react()]
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "default",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.cjs",
|
||||||
|
"css": "@/styles/globals.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/utils",
|
||||||
|
"styles": "@/styles"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include mime.types;
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_buffers 16 8k;
|
||||||
|
gzip_http_version 1.1;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /error_page.html {
|
||||||
|
internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|htm|html)$ {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
expires 30d;
|
||||||
|
add_header Pragma public;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 404 /error_page.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "astroad",
|
||||||
|
"description": "Astroad - Astro",
|
||||||
|
"type": "module",
|
||||||
|
"version": "1.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev --host",
|
||||||
|
"build": "astro build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/image": "0.18.0",
|
||||||
|
"@astrojs/prefetch": "0.4.1",
|
||||||
|
"@astrojs/react": "^3.1.1",
|
||||||
|
"@astrojs/sitemap": "3.1.2",
|
||||||
|
"@astrojs/tailwind": "5.1.0",
|
||||||
|
"@hookform/resolvers": "^3.3.4",
|
||||||
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"@tanstack/react-query": "^5.28.14",
|
||||||
|
"@types/leaflet": "^1.9.8",
|
||||||
|
"@types/react": "^18.2.74",
|
||||||
|
"@types/react-dom": "^18.2.23",
|
||||||
|
"astro": "^4.5.13",
|
||||||
|
"axios": "^1.6.8",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
|
"css-select": "5.1.0",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
|
"lucide-react": "^0.364.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-hook-form": "^7.51.2",
|
||||||
|
"react-leaflet": "^4.2.1",
|
||||||
|
"sharp": "^0.32.6",
|
||||||
|
"slate-serializers": "0.4.1",
|
||||||
|
"tailwind-merge": "^2.2.2",
|
||||||
|
"tailwindcss": "^3.3.3",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"typescript": "^5.4.3",
|
||||||
|
"zod": "^3.22.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^3.0.2",
|
||||||
|
"prettier-plugin-astro": "^0.11.1",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 749 B After Width: | Height: | Size: 749 B |
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,67 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
|
||||||
|
|
||||||
|
export function AddRetailerForm() {
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
username: z.string().min(2, {
|
||||||
|
message: "Username must be at least 2 characters.",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
username: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||||
|
// Do something with the form values.
|
||||||
|
// ✅ This will be type-safe and validated according to z schema.
|
||||||
|
console.log(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<div>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="username"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Username</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
This is your public display name.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button variant={"default"} type="submit">Submit</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { useQuery, useMutation, useQueryClient, queryOptions, QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { KiosMap } from "@/components/KiosMap"
|
||||||
|
|
||||||
|
export const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
staleTime: 30000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface AppProps {
|
||||||
|
children?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const App: React.FC<AppProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="app">
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
{props.children}
|
||||||
|
<KiosMap/>
|
||||||
|
</QueryClientProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { MapContainer, TileLayer, Marker, CircleMarker, Popup, Polyline, LayerGroup, GeoJSON, } from 'react-leaflet';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import L, { LatLngBounds } from 'leaflet';
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient, queryOptions } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
//Todo: Move types to own file
|
||||||
|
type User = {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Product = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
weight?: number;
|
||||||
|
picture: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// type Location = {
|
||||||
|
// latitude: number;
|
||||||
|
// longitude: number;
|
||||||
|
// }
|
||||||
|
|
||||||
|
type Maker = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
phoneNumber?: string;
|
||||||
|
location: [number, number];
|
||||||
|
stock: Product[];
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Retailer = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
phoneNumber?: string;
|
||||||
|
location: [number, number];
|
||||||
|
stock: Product[];
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DISPATCH_STATUS = ['requested', 'accepted', 'archived'] as const;
|
||||||
|
type DispatchStatus = typeof DISPATCH_STATUS[number];
|
||||||
|
|
||||||
|
type Dispatch = {
|
||||||
|
id: string;
|
||||||
|
dispatchesCode?: string; //Human readable id
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
|
||||||
|
maker?: Maker;
|
||||||
|
retailer?: Retailer;
|
||||||
|
products: Product[];
|
||||||
|
|
||||||
|
courier?: User;
|
||||||
|
|
||||||
|
timeSensitive: boolean;
|
||||||
|
status: DispatchStatus[];
|
||||||
|
|
||||||
|
departureDate: string;
|
||||||
|
arrivalDate: string;
|
||||||
|
weightAllowance: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Todo: update fetch url endpoints
|
||||||
|
//Todo: Move queryclient and hooks to own file
|
||||||
|
//Todo: Move axios stuff
|
||||||
|
const API_URL = "http://localhost:3001"
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const getMakers = async () => {
|
||||||
|
const url = `${API_URL}/api/makers`
|
||||||
|
console.log("Fetching url:", url)
|
||||||
|
const response = await axios.get(url);
|
||||||
|
|
||||||
|
const makers: Maker[] = response.data.docs;
|
||||||
|
console.log(`Fetch result from ${url}`, makers)
|
||||||
|
return makers;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const useGetMakers = () => {
|
||||||
|
return useQuery<Maker[]>({
|
||||||
|
queryFn: () => getMakers(),
|
||||||
|
queryKey: ['makers'],
|
||||||
|
enabled: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRetailers = async () => {
|
||||||
|
const url = `${API_URL}/api/retailers`
|
||||||
|
console.log("Fetching url:", url)
|
||||||
|
const response = await axios.get(url);
|
||||||
|
|
||||||
|
const retailers:Retailer[] = response.data.docs;
|
||||||
|
console.log(`Fetch result from ${url}`, retailers)
|
||||||
|
return retailers;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const useGetRetailers = () => {
|
||||||
|
return useQuery<Retailer[]>({
|
||||||
|
queryFn: () => getRetailers(),
|
||||||
|
queryKey: ['retailers'],
|
||||||
|
enabled: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDispatches = async () => {
|
||||||
|
const url = `${API_URL}/api/dispatches`
|
||||||
|
console.log("Fetching url:", url)
|
||||||
|
const response = await axios.get(url);
|
||||||
|
|
||||||
|
const dispatches:Dispatch[] = response.data.docs;
|
||||||
|
console.log(`Fetch result from ${url}`, dispatches)
|
||||||
|
return dispatches;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const useGetDispatches = () => {
|
||||||
|
return useQuery<Dispatch[]>({
|
||||||
|
queryFn: () => getDispatches(),
|
||||||
|
queryKey: ['retailers'],
|
||||||
|
enabled: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const KiosMap = () => {
|
||||||
|
|
||||||
|
const { data: makers, isLoading: isLoadingMakers } = useGetMakers();
|
||||||
|
const { data: retailers, isLoading: isLoadingRetailers } = useGetRetailers();
|
||||||
|
const { data: dispatches, isLoading: isLoadingDispatches } = useGetDispatches();
|
||||||
|
|
||||||
|
const blackDotIcon = L.divIcon({
|
||||||
|
className: 'bg-gray-950 rounded-full',
|
||||||
|
iconSize: [20, 20],
|
||||||
|
iconAnchor: [10, 10]
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const bounds = new LatLngBounds(
|
||||||
|
[-90, -180], // Southwest corner
|
||||||
|
[90, 180] // Northeast corner
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full flex justify-center align-middle'>
|
||||||
|
|
||||||
|
<MapContainer
|
||||||
|
id="map"
|
||||||
|
center={[-6.1815, 106.8228]}
|
||||||
|
zoom={3}
|
||||||
|
maxBounds={bounds} // Restrict panning beyond these bounds
|
||||||
|
maxBoundsViscosity={0.9} // How strongly to snap the map's bounds to the restricted area
|
||||||
|
style={{ height: '800px', width: '100%' }}
|
||||||
|
>
|
||||||
|
<TileLayer url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
|
maxZoom={19}
|
||||||
|
attribution='© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||||
|
/>
|
||||||
|
|
||||||
|
{makers &&
|
||||||
|
<LayerGroup>
|
||||||
|
{makers.map((maker: any, index: number) => (
|
||||||
|
<Marker
|
||||||
|
key={index}
|
||||||
|
position={[maker.location[0], maker.location[1]]}
|
||||||
|
icon={blackDotIcon}
|
||||||
|
>
|
||||||
|
<Popup>{maker.name}</Popup>
|
||||||
|
</Marker>
|
||||||
|
))}
|
||||||
|
</LayerGroup>
|
||||||
|
}
|
||||||
|
|
||||||
|
{retailers &&
|
||||||
|
<LayerGroup>
|
||||||
|
{retailers.map((retailer: any, index: number) => (
|
||||||
|
<Marker
|
||||||
|
key={index}
|
||||||
|
position={[retailer.location[0], retailer.location[1]]}
|
||||||
|
icon={blackDotIcon}
|
||||||
|
>
|
||||||
|
<Popup>{retailer.name}</Popup>
|
||||||
|
</Marker>
|
||||||
|
))}
|
||||||
|
</LayerGroup>
|
||||||
|
}
|
||||||
|
|
||||||
|
{dispatches &&
|
||||||
|
<LayerGroup>
|
||||||
|
{dispatches.map((dispatch: any, index: number) => {
|
||||||
|
const start = dispatch.maker.location;
|
||||||
|
const end = dispatch.retailer.location;
|
||||||
|
|
||||||
|
let productsString = '';
|
||||||
|
dispatch.products.forEach((product: any, i: number) => {
|
||||||
|
productsString += product.productTitle + (i + 1 < dispatch.products.length ? ', ' : '');
|
||||||
|
});
|
||||||
|
|
||||||
|
const myDashArray =
|
||||||
|
dispatch.status === 'requested' ? '20, 10' :
|
||||||
|
dispatch.status === 'archived' ? '1, 5' :
|
||||||
|
'0, 0';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={index}>
|
||||||
|
<Marker position={[start[0], start[1]]}
|
||||||
|
icon={blackDotIcon}
|
||||||
|
>
|
||||||
|
<Popup>{dispatch.startingPoint.name}</Popup>
|
||||||
|
</Marker>
|
||||||
|
<Marker position={[end[0], end[1]]}
|
||||||
|
icon={blackDotIcon}
|
||||||
|
>
|
||||||
|
<Popup>{dispatch.endPoint.name}</Popup>
|
||||||
|
</Marker>
|
||||||
|
<Polyline
|
||||||
|
positions={[[start[0], start[1]], [end[0], end[1]]]}
|
||||||
|
color="#000"
|
||||||
|
dashArray={myDashArray} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</LayerGroup>
|
||||||
|
}
|
||||||
|
</MapContainer >
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user