├── .gitignore ├── .prettierrc ├── README.md ├── api ├── .dockerignore ├── .env ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── develop.sh ├── docker-compose.local.yml ├── extensions │ ├── displays │ │ └── .gitkeep │ ├── endpoints │ │ └── .gitkeep │ ├── hooks │ │ └── .gitkeep │ ├── interfaces │ │ └── .gitkeep │ ├── layouts │ │ └── .gitkeep │ ├── migrations │ │ └── .gitkeep │ ├── modules │ │ └── .gitkeep │ ├── operations │ │ └── .gitkeep │ └── panels │ │ └── .gitkeep ├── knexfile.js ├── package.json ├── schema.yaml ├── seeds │ ├── 00-developmentToken.ts │ └── articles.ts ├── tsconfig.json └── types │ ├── Articles.ts │ ├── Status.ts │ ├── Users.ts │ └── index.ts ├── package.json ├── packages ├── configs │ ├── jest │ │ └── jest-common.js │ ├── package.json │ ├── tailwind │ │ └── tailwind.config.js │ └── tsconfig │ │ ├── base.json │ │ └── react-library.json └── ui │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── .storybook │ ├── main.js │ └── preview.js │ ├── babel.config.js │ ├── jest.config.js │ ├── package.json │ ├── postcss.config.js │ ├── src │ ├── Avatar │ │ ├── Avatar.mocks.ts │ │ ├── Avatar.props.ts │ │ ├── Avatar.stories.tsx │ │ ├── Avatar.test.tsx │ │ ├── Avatar.tsx │ │ ├── Avatar.utils.ts │ │ └── index.ts │ ├── global.css │ └── index.ts │ ├── tailwind.config.js │ └── tsconfig.json ├── page ├── .env ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── app.arc ├── app │ ├── api │ │ └── index.ts │ ├── entry.client.tsx │ ├── entry.server.tsx │ ├── root.tsx │ └── routes │ │ ├── blog │ │ └── $slug.tsx │ │ └── index.tsx ├── cypress.config.ts ├── cypress │ ├── e2e │ │ └── smoke.cy.ts │ ├── fixtures │ │ └── example.json │ ├── support │ │ ├── commands.ts │ │ └── e2e.ts │ └── tsconfig.json ├── jest.config.js ├── package.json ├── public │ ├── favicon.ico │ └── images │ │ └── cool-stack-cover.png ├── remix.config.js ├── remix.env.d.ts ├── server.js ├── server │ └── config.arc ├── tailwind.config.js └── tsconfig.json ├── renovate.json ├── turbo.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | .pnp 4 | .pnp.js 5 | 6 | # testing 7 | coverage 8 | 9 | # artifacts 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | *.pem 15 | **/tsconfig.tsbuildinfo 16 | 17 | # debug 18 | yarn-debug.log* 19 | yarn-error.log* 20 | 21 | # env files 22 | .env 23 | .env.* 24 | !.env.example 25 | 26 | # turborepo 27 | **/.turbo 28 | 29 | # log 30 | **/.log* 31 | 32 | # IDE 33 | .vscode 34 | .idea 35 | 36 | # Storybook 37 | **/storybook-static 38 | **/build-storybook.log 39 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | "@tdsoft/prettier-config" 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Cool-Stack Cover 3 |
4 | 5 |

6 | Ice-cool 🧊 Remix + Directus starter template. 7 |

8 | 9 |
10 | 11 | 12 |
13 | 14 | by TDSOFT Logo - Directus Experts 15 | 16 |
17 |
18 |
19 | 20 | ## Overview 🔭 21 | 22 | 🧊 _cool-stack_ 🧊 is a modern stack for building web applications. It's built on top of two powerful tools: 23 | 24 | - 💿 Remix - blazingly fast, powerful React.js framework that is used for server-side rendering, allowing you to focus on creating beautiful user interfaces. 25 | 26 | - 🐰 Directus - an instant REST and GraphQL API that seamlessly integrates on top of a Postgres SQL database. Directus has an easy to work with SDK, that blends perfectly with Remix. 27 | 28 |

29 | ❄️ Get cool-stack today, before your time melts away ❄️ 30 |

31 | 32 | ## What's _cool_ in the stack? ✨ 33 | 34 | - [Remix](https://remix.run/) as a web framework (using [Architect](https://arc.codes/)) 35 | - Content API and dashboard powered by [Directus](https://directus.io/) 36 | - [PosgreSQL](https://www.postgresql.org/) + [pgAdmin](https://www.pgadmin.org/) for data persistence 37 | - API seeding with [Knex.js](https://knexjs.org/) for local development 38 | - [Tailwind CSS](https://tailwindcss.com/) for styling 39 | - [Storybook](https://storybook.js.org/) for UI development 40 | - [Turborepo](https://turbo.build/repo) as a monorepo and build system 41 | - [Cypress](https://cypress.io) - E2E testing framework 42 | - Unit testing with [Jest](https://jestjs.io/) and [Testing Library](https://testing-library.com) 43 | - Code formatting with [Prettier](https://prettier.io) 44 | - Linting with [ESLint](https://eslint.org) 45 | 46 | ## Prerequisites 47 | 48 | - [node](https://nodejs.org/) - at least v14 49 | - [yarn](https://yarnpkg.com/) - at least v1.22.19 50 | - [Docker Desktop](https://docs.docker.com/desktop/) - for spinning up your local PosgreSQL database & pgAdmin. If you don't want to run a database on your local machine, you can use the free tier of [Directus cloud services](https://directus.cloud/) 51 | 52 | ## How to start 53 | 54 | - Run `yarn run bootstrap` - that will install and build all the project dependencies & packages 55 | - Run `yarn run dev` - turborepo will spin up all 'dev' scripts across the monorepo. You can also run each project's dev scripts separately 56 | - Log in to your directus dashboard using these credentials `user: admin@example.com | password: admin` 57 | - For production usage, make sure to fill in your `.env` files in api & page directories. It's best not to version these files in your repository 58 | 59 | ## Deployment 60 | 61 | ### Page 62 | 63 | The Remix app in this repository comes with an [Architect](https://arc.codes/) adapter, as it's working best for our use cases. 64 | 65 | Refer to [architect's docs](https://arc.codes/docs/en/reference/cli/deploy) for the a detailed guide on that topic. 66 | 67 | ### API 68 | 69 | Refer to [directus docs](https://docs.directus.io/self-hosted/quickstart.html) for a guide on how to deploy a self-hosted directus instance. 70 | 71 | We recommend deploying to [AWS Elastic Beanstalk](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_nodejs.html) using Docker. 72 | 73 |
74 | 75 |

76 | Watch episode of "Directus I-MADE-THIS" podcast 77 |

78 | 79 |
80 | 81 | Directus I-MADE-THIS podcast cover 82 | 83 |
84 | 85 |
86 | 87 | ## Built with cool-stack 88 | 89 | - [BettorSignals](https://bettorsignals.com/) - Multi-channel sports tipping platform 90 | -------------------------------------------------------------------------------- /api/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .turbo 3 | yarn-debug.log* 4 | yarn-error.log* 5 | -------------------------------------------------------------------------------- /api/.env: -------------------------------------------------------------------------------- 1 | #################################################################################################### 2 | # 3 | # These values set environment variables which modify core settings of Directus. 4 | # 5 | # Values in square brackets are the default values. 6 | # 7 | # The following options are not all possible options. For more, see 8 | # https://docs.directus.io/self-hosted/config-options/ 9 | # 10 | #################################################################################################### 11 | #################################################################################################### 12 | 13 | ### General 14 | # Development token 15 | DEVELOPMENT_TOKEN="token" 16 | 17 | # Initial admin account 18 | ADMIN_EMAIL="admin@example.com" 19 | ADMIN_PASSWORD="admin" 20 | 21 | # IP or host the API listens on ["0.0.0.0"] 22 | HOST="0.0.0.0" 23 | 24 | # The port Directus will run on [8055] 25 | PORT=8055 26 | 27 | # The URL where your API can be reached on the web. It is also used for things like OAuth redirects, 28 | # forgot-password emails, and logos that needs to be publicly available on the internet. ["/"] 29 | PUBLIC_URL="/" 30 | # PUBLIC_URL="http://localhost:8055" 31 | 32 | # What level of detail to log. [info] 33 | # "fatal", "error", "warn", "info", "debug", "trace", "silent" 34 | # LOG_LEVEL="info" 35 | 36 | # Render the logs human readable (pretty) or as JSON (raw), [pretty] 37 | # "pretty", "raw" 38 | # LOG_STYLE="pretty" 39 | 40 | # Controls the maximum request body size. Accepts number of bytes, or human readable string ["100kb"] 41 | # MAX_PAYLOAD_SIZE="100kb" 42 | 43 | # Where to redirect to when navigating to /. Accepts a relative path, absolute URL, or false to disable ["./admin"] 44 | # ROOT_REDIRECT="./admin" 45 | 46 | # Whether or not to serve the Admin App under /admin. [true] 47 | # SERVE_APP=true 48 | 49 | # Whether or not to enable GraphQL Introspection [true] 50 | # GRAPHQL_INTROSPECTION=true 51 | 52 | #################################################################################################### 53 | ### Database 54 | 55 | # All DB_* environment variables are passed to the connection configuration of a Knex instance. 56 | # Based on your project's needs, you can extend the DB_* environment variables with any config 57 | # you need to pass to the database instance. 58 | 59 | DB_CLIENT="pg" 60 | DB_HOST="127.0.0.1" 61 | DB_PORT="5432" 62 | DB_DATABASE="postgres" 63 | DB_USER="postgres" 64 | DB_PASSWORD="pass123" 65 | DB_SSL="false" 66 | 67 | ## Postgres 68 | # DB_CLIENT="pg" 69 | # DB_HOST="localhost" 70 | # DB_PORT=5432 71 | # DB_DATABASE="directus" 72 | # DB_USER="postgres" 73 | # DB_PASSWORD="secret" 74 | 75 | ## CockroachDB 76 | # DB_CLIENT="cockroachdb" 77 | # DB_HOST="localhost" 78 | # DB_PORT=26257 79 | # DB_DATABASE="directus" 80 | # DB_USER="root" 81 | # DB_PASSWORD="" 82 | 83 | ## MySQL 8 84 | # DB_CLIENT="mysql" 85 | # DB_HOST="localhost" 86 | # DB_PORT=3306 87 | # DB_DATABASE="directus" 88 | # DB_USER="root" 89 | # DB_PASSWORD="secret" 90 | 91 | ## MariaDB 92 | # DB_CLIENT="mysql" 93 | # DB_HOST="localhost" 94 | # DB_PORT=3306 95 | # DB_DATABASE="directus" 96 | # DB_USER="root" 97 | # DB_PASSWORD="secret" 98 | 99 | ## MS SQL 100 | # DB_CLIENT="mssql" 101 | # DB_HOST="localhost" 102 | # DB_PORT=1343 103 | # DB_DATABASE="directus" 104 | # DB_USER="sa" 105 | # DB_PASSWORD="Test@123" 106 | 107 | ## OracleDB 108 | # DB_CLIENT="oracledb" 109 | # DB_CONNECT_STRING="localhost:1521/XE" 110 | # DB_USER="secretsysuser" 111 | # DB_PASSWORD="secretpassword" 112 | 113 | ## SQLite Example 114 | # DB_CLIENT="sqlite3" 115 | # DB_FILENAME="./data.db" 116 | 117 | ## MySQL 5.7 118 | # DB_CLIENT="mysql" 119 | # DB_HOST="localhost" 120 | # DB_PORT=3306 121 | # DB_DATABASE="directus" 122 | # DB_USER="root" 123 | # DB_PASSWORD="secret" 124 | 125 | #################################################################################################### 126 | ### Rate Limiting 127 | 128 | # Whether or not to enable rate limiting on the API [false] 129 | RATE_LIMITER_ENABLED=false 130 | 131 | # Where to store the rate limiter counts [memory] 132 | # memory, redis, memcache 133 | RATE_LIMITER_STORE=memory 134 | # RATE_LIMITER_REDIS="redis://@127.0.0.1:5105" 135 | # RATE_LIMITER_MEMCACHE="localhost:5109" 136 | 137 | # The amount of allowed hits per duration [50] 138 | RATE_LIMITER_POINTS=25 139 | 140 | # The time window in seconds in which the hits are counted [1] 141 | RATE_LIMITER_DURATION=1 142 | 143 | #################################################################################################### 144 | ### Caching 145 | 146 | # Whether or not caching is enabled. [false] 147 | CACHE_ENABLED=false 148 | 149 | # How long the cache is persisted ["5m"] 150 | # CACHE_TTL="30m" 151 | 152 | # How to scope the cache data ["directus-cache"] 153 | # CACHE_NAMESPACE="directus-cache" 154 | 155 | # Automatically purge the cache on create, update, and delete actions. [false] 156 | # CACHE_AUTO_PURGE=true 157 | 158 | # memory | redis | memcache 159 | CACHE_STORE=memory 160 | 161 | # How long assets will be cached for in the browser. Sets the max-age value of the Cache-Control header ["30m"] 162 | ASSETS_CACHE_TTL="30m" 163 | 164 | # CACHE_REDIS="redis://@127.0.0.1:5105" 165 | # CACHE_MEMCACHE="localhost:5109" 166 | 167 | #################################################################################################### 168 | ### File Storage 169 | 170 | # A CSV of storage locations (eg: local,digitalocean,amazon) to use. You can use any names you'd like for these keys ["local"] 171 | STORAGE_LOCATIONS="local" 172 | STORAGE_LOCAL_DRIVER="local" 173 | STORAGE_LOCAL_ROOT="./uploads" 174 | 175 | ## S3 Example (location name: DigitalOcean) 176 | # STORAGE_DIGITALOCEAN_DRIVER="s3" 177 | # STORAGE_DIGITALOCEAN_KEY="abcdef" 178 | # STORAGE_DIGITALOCEAN_SECRET="ghijkl" 179 | # STORAGE_DIGITALOCEAN_ENDPOINT="ams3.digitaloceanspaces.com" 180 | # STORAGE_DIGITALOCEAN_BUCKET="my-files" 181 | # STORAGE_DIGITALOCEAN_REGION="ams3" 182 | 183 | ## Google Cloud Storage Example (location name: Google) 184 | # STORAGE_GOOGLE_DRIVER="gcs" 185 | # STORAGE_GOOGLE_KEY_FILENAME="abcdef" 186 | # STORAGE_GOOGLE_BUCKET="my-files" 187 | 188 | 189 | ## A comma-separated list of metadata keys to collect during file upload. Use * for all 190 | # Extracting all metadata might cause memory issues when the file has an unusually large set of metadata 191 | # [ifd0.Make,ifd0.Model,exif.FNumber,exif.ExposureTime,exif.FocalLength,exif.ISO] 192 | # FILE_METADATA_ALLOW_LIST= 193 | 194 | #################################################################################################### 195 | ### Security 196 | 197 | KEY="key" 198 | SECRET="secret" 199 | 200 | 201 | # Unique identifier for the project 202 | # KEY="xxxxxxx-xxxxxx-xxxxxxxx-xxxxxxxxxx" 203 | 204 | # Secret string for the project 205 | # SECRET="abcdef" 206 | 207 | # The duration that the access token is valid ["15m"] 208 | ACCESS_TOKEN_TTL="15m" 209 | 210 | # The duration that the refresh token is valid, and also how long users stay logged-in to the App ["7d"] 211 | REFRESH_TOKEN_TTL="7d" 212 | 213 | # Whether or not to use a secure cookie for the refresh token in cookie mode [false] 214 | REFRESH_TOKEN_COOKIE_SECURE=false 215 | 216 | # Value for sameSite in the refresh token cookie when in cookie mode ["lax"] 217 | REFRESH_TOKEN_COOKIE_SAME_SITE="lax" 218 | 219 | # Name of refresh token cookie ["directus_refresh_token"] 220 | REFRESH_TOKEN_COOKIE_NAME="directus_refresh_token" 221 | 222 | # Which domain to use for the refresh cookie. Useful for development mode. 223 | # REFRESH_TOKEN_COOKIE_DOMAIN 224 | 225 | # Whether or not to enable the CORS headers [false] 226 | CORS_ENABLED=true 227 | 228 | # Value for the Access-Control-Allow-Origin header. Use true to match the Origin header, or provide a domain or a CSV of domains for specific access [false] 229 | CORS_ORIGIN=true 230 | 231 | # Value for the Access-Control-Allow-Methods header [GET,POST,PATCH,DELETE] 232 | CORS_METHODS=GET,POST,PATCH,DELETE 233 | 234 | # Value for the Access-Control-Allow-Headers header [Content-Type,Authorization] 235 | CORS_ALLOWED_HEADERS=Content-Type,Authorization 236 | 237 | # Value for the Access-Control-Expose-Headers header [Content-R 238 | CORS_EXPOSED_HEADERS=Content-Range 239 | 240 | # Whether or not to send the Access-Control-Allow-Credentials header [true] 241 | CORS_CREDENTIALS=true 242 | 243 | # Value for the Access-Control-Max-Age header [18000] 244 | CORS_MAX_AGE=18000 245 | 246 | #################################################################################################### 247 | ### Argon2 248 | 249 | # How much memory to use when generating hashes, in KiB [4096] 250 | # HASH_MEMORY_COST=81920 251 | 252 | # The length of the hash function output in bytes [32] 253 | # HASH_HASH_LENGTH=32 254 | 255 | # The amount of passes (iterations) used by the hash function. It increases hash strength at the cost of time required to compute [3] 256 | # HASH_TIME_COST=10 257 | 258 | # The amount of threads to compute the hash on. Each thread has a memory pool with HASH_MEMORY_COST size [1] 259 | # HASH_PARALLELISM=2 260 | 261 | # The variant of the hash function (0: argon2d, 1: argon2i, or 2: argon2id) [1] 262 | # HASH_TYPE=2 263 | 264 | # An extra and optional non-secret value. The value will be included B64 encoded in the parameters portion of the digest [] 265 | # HASH_ASSOCIATED_DATA=foo 266 | 267 | #################################################################################################### 268 | ### Auth Providers 269 | 270 | # A comma-separated list of auth providers [] 271 | AUTH_PROVIDERS="" 272 | # AUTH_PROVIDERS="github" 273 | 274 | # AUTH_GITHUB_DRIVER="oauth2" 275 | # AUTH_GITHUB_CLIENT_ID="73e...4b" 276 | # AUTH_GITHUB_CLIENT_SECRET="b9...98" 277 | # AUTH_GITHUB_AUTHORIZE_URL="https://github.com/login/oauth/authorize" 278 | # AUTH_GITHUB_ACCESS_URL="https://github.com/login/oauth/access_token" 279 | # AUTH_GITHUB_PROFILE_URL="https://api.github.com/user" 280 | # AUTH_GITHUB_ALLOW_PUBLIC_REGISTRATION=true 281 | # AUTH_GITHUB_DEFAULT_ROLE_ID="82424427-c9d4-4289-8bc5-ed1bf8422c90" 282 | # AUTH_GITHUB_ICON="github" 283 | # AUTH_GITHUB_EMAIL_KEY="email" 284 | # AUTH_GITHUB_IDENTIFIER_KEY="login" 285 | 286 | #################################################################################################### 287 | ### Extensions 288 | 289 | # Path to your local extensions folder ["./extensions"] 290 | EXTENSIONS_PATH="./extensions" 291 | 292 | # Automatically reload extensions when they have changed [false] 293 | EXTENSIONS_AUTO_RELOAD=false 294 | 295 | #################################################################################################### 296 | ### Email 297 | 298 | # Email address from which emails are sent ["no-reply@directus.io"] 299 | EMAIL_FROM="no-reply@directus.io" 300 | 301 | # What to use to send emails. One of 302 | # sendmail, smtp, mailgun, ses. 303 | EMAIL_TRANSPORT="sendmail" 304 | EMAIL_SENDMAIL_NEW_LINE="unix" 305 | EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail" 306 | 307 | ## Email (Sendmail Transport) 308 | 309 | # What new line style to use in sendmail ["unix"] 310 | # EMAIL_SENDMAIL_NEW_LINE="unix" 311 | 312 | # Path to your sendmail executable ["/usr/sbin/sendmail"] 313 | # EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail" 314 | 315 | ## Email (SMTP Transport) 316 | # EMAIL_SMTP_HOST="localhost" 317 | 318 | # Use SMTP pooling 319 | # EMAIL_SMTP_POOL=true 320 | # EMAIL_SMTP_PORT=465 321 | # EMAIL_SMTP_SECURE=false # Use TLS 322 | # EMAIL_SMTP_IGNORE_TLS=false 323 | # EMAIL_SMTP_USER="username" 324 | # EMAIL_SMTP_PASSWORD="password" 325 | 326 | ## Email (Mailgun Transport) 327 | # EMAIL_MAILGUN_API_KEY="key-1234123412341234" 328 | # EMAIL_MAILGUN_DOMAIN="a domain name from https://app.mailgun.com/app/sending/domains" 329 | -------------------------------------------------------------------------------- /api/.env.example: -------------------------------------------------------------------------------- 1 | # Uncommented values should be filled/configured inside your .env file(s). 2 | # Commented ones are just for the reference. 3 | 4 | #################################################################################################### 5 | # 6 | # These values set environment variables which modify core settings of Directus. 7 | # 8 | # Values in square brackets are the default values. 9 | # 10 | # The following options are not all possible options. For more, see 11 | # https://docs.directus.io/self-hosted/config-options/ 12 | # 13 | #################################################################################################### 14 | #################################################################################################### 15 | 16 | ### General 17 | # Development token 18 | DEVELOPMENT_TOKEN="token" 19 | 20 | # Initial admin account 21 | ADMIN_EMAIL="admin@example.com" 22 | ADMIN_PASSWORD="admin" 23 | 24 | # IP or host the API listens on ["0.0.0.0"] 25 | HOST="0.0.0.0" 26 | 27 | # The port Directus will run on [8055] 28 | PORT=8055 29 | 30 | # The URL where your API can be reached on the web. It is also used for things like OAuth redirects, 31 | # forgot-password emails, and logos that needs to be publicly available on the internet. ["/"] 32 | PUBLIC_URL="/" 33 | # PUBLIC_URL="http://localhost:8055" 34 | 35 | # What level of detail to log. [info] 36 | # "fatal", "error", "warn", "info", "debug", "trace", "silent" 37 | # LOG_LEVEL="info" 38 | 39 | # Render the logs human readable (pretty) or as JSON (raw), [pretty] 40 | # "pretty", "raw" 41 | # LOG_STYLE="pretty" 42 | 43 | # Controls the maximum request body size. Accepts number of bytes, or human readable string ["100kb"] 44 | # MAX_PAYLOAD_SIZE="100kb" 45 | 46 | # Where to redirect to when navigating to /. Accepts a relative path, absolute URL, or false to disable ["./admin"] 47 | # ROOT_REDIRECT="./admin" 48 | 49 | # Whether or not to serve the Admin App under /admin. [true] 50 | # SERVE_APP=true 51 | 52 | # Whether or not to enable GraphQL Introspection [true] 53 | # GRAPHQL_INTROSPECTION=true 54 | 55 | #################################################################################################### 56 | ### Database 57 | 58 | # All DB_* environment variables are passed to the connection configuration of a Knex instance. 59 | # Based on your project's needs, you can extend the DB_* environment variables with any config 60 | # you need to pass to the database instance. 61 | 62 | DB_CLIENT="pg" 63 | DB_HOST="127.0.0.1" 64 | DB_PORT="5432" 65 | DB_DATABASE="postgres" 66 | DB_USER="postgres" 67 | DB_PASSWORD="pass123" 68 | DB_SSL="false" 69 | 70 | ## Postgres 71 | # DB_CLIENT="pg" 72 | # DB_HOST="localhost" 73 | # DB_PORT=5432 74 | # DB_DATABASE="directus" 75 | # DB_USER="postgres" 76 | # DB_PASSWORD="secret" 77 | 78 | ## CockroachDB 79 | # DB_CLIENT="cockroachdb" 80 | # DB_HOST="localhost" 81 | # DB_PORT=26257 82 | # DB_DATABASE="directus" 83 | # DB_USER="root" 84 | # DB_PASSWORD="" 85 | 86 | ## MySQL 8 87 | # DB_CLIENT="mysql" 88 | # DB_HOST="localhost" 89 | # DB_PORT=3306 90 | # DB_DATABASE="directus" 91 | # DB_USER="root" 92 | # DB_PASSWORD="secret" 93 | 94 | ## MariaDB 95 | # DB_CLIENT="mysql" 96 | # DB_HOST="localhost" 97 | # DB_PORT=3306 98 | # DB_DATABASE="directus" 99 | # DB_USER="root" 100 | # DB_PASSWORD="secret" 101 | 102 | ## MS SQL 103 | # DB_CLIENT="mssql" 104 | # DB_HOST="localhost" 105 | # DB_PORT=1343 106 | # DB_DATABASE="directus" 107 | # DB_USER="sa" 108 | # DB_PASSWORD="Test@123" 109 | 110 | ## OracleDB 111 | # DB_CLIENT="oracledb" 112 | # DB_CONNECT_STRING="localhost:1521/XE" 113 | # DB_USER="secretsysuser" 114 | # DB_PASSWORD="secretpassword" 115 | 116 | ## SQLite Example 117 | # DB_CLIENT="sqlite3" 118 | # DB_FILENAME="./data.db" 119 | 120 | ## MySQL 5.7 121 | # DB_CLIENT="mysql" 122 | # DB_HOST="localhost" 123 | # DB_PORT=3306 124 | # DB_DATABASE="directus" 125 | # DB_USER="root" 126 | # DB_PASSWORD="secret" 127 | 128 | #################################################################################################### 129 | ### Rate Limiting 130 | 131 | # Whether or not to enable rate limiting on the API [false] 132 | RATE_LIMITER_ENABLED=false 133 | 134 | # Where to store the rate limiter counts [memory] 135 | # memory, redis, memcache 136 | RATE_LIMITER_STORE=memory 137 | # RATE_LIMITER_REDIS="redis://@127.0.0.1:5105" 138 | # RATE_LIMITER_MEMCACHE="localhost:5109" 139 | 140 | # The amount of allowed hits per duration [50] 141 | RATE_LIMITER_POINTS=25 142 | 143 | # The time window in seconds in which the hits are counted [1] 144 | RATE_LIMITER_DURATION=1 145 | 146 | #################################################################################################### 147 | ### Caching 148 | 149 | # Whether or not caching is enabled. [false] 150 | CACHE_ENABLED=false 151 | 152 | # How long the cache is persisted ["5m"] 153 | # CACHE_TTL="30m" 154 | 155 | # How to scope the cache data ["directus-cache"] 156 | # CACHE_NAMESPACE="directus-cache" 157 | 158 | # Automatically purge the cache on create, update, and delete actions. [false] 159 | # CACHE_AUTO_PURGE=true 160 | 161 | # memory | redis | memcache 162 | CACHE_STORE=memory 163 | 164 | # How long assets will be cached for in the browser. Sets the max-age value of the Cache-Control header ["30m"] 165 | ASSETS_CACHE_TTL="30m" 166 | 167 | # CACHE_REDIS="redis://@127.0.0.1:5105" 168 | # CACHE_MEMCACHE="localhost:5109" 169 | 170 | #################################################################################################### 171 | ### File Storage 172 | 173 | # A CSV of storage locations (eg: local,digitalocean,amazon) to use. You can use any names you'd like for these keys ["local"] 174 | STORAGE_LOCATIONS="local" 175 | STORAGE_LOCAL_DRIVER="local" 176 | STORAGE_LOCAL_ROOT="./uploads" 177 | 178 | ## S3 Example (location name: DigitalOcean) 179 | # STORAGE_DIGITALOCEAN_DRIVER="s3" 180 | # STORAGE_DIGITALOCEAN_KEY="abcdef" 181 | # STORAGE_DIGITALOCEAN_SECRET="ghijkl" 182 | # STORAGE_DIGITALOCEAN_ENDPOINT="ams3.digitaloceanspaces.com" 183 | # STORAGE_DIGITALOCEAN_BUCKET="my-files" 184 | # STORAGE_DIGITALOCEAN_REGION="ams3" 185 | 186 | ## Google Cloud Storage Example (location name: Google) 187 | # STORAGE_GOOGLE_DRIVER="gcs" 188 | # STORAGE_GOOGLE_KEY_FILENAME="abcdef" 189 | # STORAGE_GOOGLE_BUCKET="my-files" 190 | 191 | 192 | ## A comma-separated list of metadata keys to collect during file upload. Use * for all 193 | # Extracting all metadata might cause memory issues when the file has an unusually large set of metadata 194 | # [ifd0.Make,ifd0.Model,exif.FNumber,exif.ExposureTime,exif.FocalLength,exif.ISO] 195 | # FILE_METADATA_ALLOW_LIST= 196 | 197 | #################################################################################################### 198 | ### Security 199 | 200 | KEY="key" 201 | SECRET="secret" 202 | 203 | 204 | # Unique identifier for the project 205 | # KEY="xxxxxxx-xxxxxx-xxxxxxxx-xxxxxxxxxx" 206 | 207 | # Secret string for the project 208 | # SECRET="abcdef" 209 | 210 | # The duration that the access token is valid ["15m"] 211 | ACCESS_TOKEN_TTL="15m" 212 | 213 | # The duration that the refresh token is valid, and also how long users stay logged-in to the App ["7d"] 214 | REFRESH_TOKEN_TTL="7d" 215 | 216 | # Whether or not to use a secure cookie for the refresh token in cookie mode [false] 217 | REFRESH_TOKEN_COOKIE_SECURE=false 218 | 219 | # Value for sameSite in the refresh token cookie when in cookie mode ["lax"] 220 | REFRESH_TOKEN_COOKIE_SAME_SITE="lax" 221 | 222 | # Name of refresh token cookie ["directus_refresh_token"] 223 | REFRESH_TOKEN_COOKIE_NAME="directus_refresh_token" 224 | 225 | # Which domain to use for the refresh cookie. Useful for development mode. 226 | # REFRESH_TOKEN_COOKIE_DOMAIN 227 | 228 | # Whether or not to enable the CORS headers [false] 229 | CORS_ENABLED=true 230 | 231 | # Value for the Access-Control-Allow-Origin header. Use true to match the Origin header, or provide a domain or a CSV of domains for specific access [false] 232 | CORS_ORIGIN=true 233 | 234 | # Value for the Access-Control-Allow-Methods header [GET,POST,PATCH,DELETE] 235 | CORS_METHODS=GET,POST,PATCH,DELETE 236 | 237 | # Value for the Access-Control-Allow-Headers header [Content-Type,Authorization] 238 | CORS_ALLOWED_HEADERS=Content-Type,Authorization 239 | 240 | # Value for the Access-Control-Expose-Headers header [Content-R 241 | CORS_EXPOSED_HEADERS=Content-Range 242 | 243 | # Whether or not to send the Access-Control-Allow-Credentials header [true] 244 | CORS_CREDENTIALS=true 245 | 246 | # Value for the Access-Control-Max-Age header [18000] 247 | CORS_MAX_AGE=18000 248 | 249 | #################################################################################################### 250 | ### Argon2 251 | 252 | # How much memory to use when generating hashes, in KiB [4096] 253 | # HASH_MEMORY_COST=81920 254 | 255 | # The length of the hash function output in bytes [32] 256 | # HASH_HASH_LENGTH=32 257 | 258 | # The amount of passes (iterations) used by the hash function. It increases hash strength at the cost of time required to compute [3] 259 | # HASH_TIME_COST=10 260 | 261 | # The amount of threads to compute the hash on. Each thread has a memory pool with HASH_MEMORY_COST size [1] 262 | # HASH_PARALLELISM=2 263 | 264 | # The variant of the hash function (0: argon2d, 1: argon2i, or 2: argon2id) [1] 265 | # HASH_TYPE=2 266 | 267 | # An extra and optional non-secret value. The value will be included B64 encoded in the parameters portion of the digest [] 268 | # HASH_ASSOCIATED_DATA=foo 269 | 270 | #################################################################################################### 271 | ### Auth Providers 272 | 273 | # A comma-separated list of auth providers [] 274 | AUTH_PROVIDERS="" 275 | # AUTH_PROVIDERS="github" 276 | 277 | # AUTH_GITHUB_DRIVER="oauth2" 278 | # AUTH_GITHUB_CLIENT_ID="73e...4b" 279 | # AUTH_GITHUB_CLIENT_SECRET="b9...98" 280 | # AUTH_GITHUB_AUTHORIZE_URL="https://github.com/login/oauth/authorize" 281 | # AUTH_GITHUB_ACCESS_URL="https://github.com/login/oauth/access_token" 282 | # AUTH_GITHUB_PROFILE_URL="https://api.github.com/user" 283 | # AUTH_GITHUB_ALLOW_PUBLIC_REGISTRATION=true 284 | # AUTH_GITHUB_DEFAULT_ROLE_ID="82424427-c9d4-4289-8bc5-ed1bf8422c90" 285 | # AUTH_GITHUB_ICON="github" 286 | # AUTH_GITHUB_EMAIL_KEY="email" 287 | # AUTH_GITHUB_IDENTIFIER_KEY="login" 288 | 289 | #################################################################################################### 290 | ### Extensions 291 | 292 | # Path to your local extensions folder ["./extensions"] 293 | EXTENSIONS_PATH="./extensions" 294 | 295 | # Automatically reload extensions when they have changed [false] 296 | EXTENSIONS_AUTO_RELOAD=false 297 | 298 | #################################################################################################### 299 | ### Email 300 | 301 | # Email address from which emails are sent ["no-reply@directus.io"] 302 | EMAIL_FROM="no-reply@directus.io" 303 | 304 | # What to use to send emails. One of 305 | # sendmail, smtp, mailgun, ses. 306 | EMAIL_TRANSPORT="sendmail" 307 | EMAIL_SENDMAIL_NEW_LINE="unix" 308 | EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail" 309 | 310 | ## Email (Sendmail Transport) 311 | 312 | # What new line style to use in sendmail ["unix"] 313 | # EMAIL_SENDMAIL_NEW_LINE="unix" 314 | 315 | # Path to your sendmail executable ["/usr/sbin/sendmail"] 316 | # EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail" 317 | 318 | ## Email (SMTP Transport) 319 | # EMAIL_SMTP_HOST="localhost" 320 | 321 | # Use SMTP pooling 322 | # EMAIL_SMTP_POOL=true 323 | # EMAIL_SMTP_PORT=465 324 | # EMAIL_SMTP_SECURE=false # Use TLS 325 | # EMAIL_SMTP_IGNORE_TLS=false 326 | # EMAIL_SMTP_USER="username" 327 | # EMAIL_SMTP_PASSWORD="password" 328 | 329 | ## Email (Mailgun Transport) 330 | # EMAIL_MAILGUN_API_KEY="key-1234123412341234" 331 | # EMAIL_MAILGUN_DOMAIN="a domain name from https://app.mailgun.com/app/sending/domains" 332 | -------------------------------------------------------------------------------- /api/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["prettier"], 3 | extends: ["eslint:recommended", "plugin:prettier/recommended", "plugin:unicorn/recommended"], 4 | env: { 5 | node: true 6 | }, 7 | ignorePatterns: ["**/*.js"], 8 | parser: "@typescript-eslint/parser", 9 | parserOptions: { 10 | project: "tsconfig.json", 11 | tsconfigRootDir: __dirname, 12 | sourceType: "module" 13 | }, 14 | rules: { 15 | "no-unused-vars": "off", 16 | "unicorn/no-null": "off", 17 | "unicorn/text-encoding-identifier-case": "off", 18 | "unicorn/filename-case": "off", 19 | "unicorn/prevent-abbreviations": [ 20 | "warn", 21 | { 22 | replacements: { 23 | args: false, 24 | props: false 25 | } 26 | } 27 | ] 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | # Elastic Beanstalk Files 2 | .elasticbeanstalk/* 3 | !.elasticbeanstalk/*.cfg.yml 4 | !.elasticbeanstalk/*.global.yml 5 | 6 | # Directus by default stores files locally, to avoid uploading them to repository we have to ignore uploads directory 7 | uploads/ 8 | -------------------------------------------------------------------------------- /api/.prettierrc: -------------------------------------------------------------------------------- 1 | "@tdsoft/prettier-config" 2 | -------------------------------------------------------------------------------- /api/develop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | yarn run stop:local:db 4 | yarn run start:local:db 5 | 6 | echo "Waiting for the database to be ready ⏳" 7 | 8 | # shellcheck disable=SC2046 9 | while [ $(docker inspect --format "{{json .State.Health.Status }}" "coolstack_postgres") != "\"healthy\"" ]; 10 | do printf "💤"; sleep 1; 11 | done 12 | 13 | directus bootstrap && directus schema apply --yes ./schema.yaml 14 | yarn run seed:local:db 15 | open-cli http://0.0.0.0:8055 16 | directus start 17 | -------------------------------------------------------------------------------- /api/docker-compose.local.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | db: 5 | container_name: coolstack_postgres 6 | image: postgres 7 | restart: always 8 | ports: 9 | - "5432:5432" 10 | environment: 11 | POSTGRES_PASSWORD: pass123 12 | networks: 13 | - postgres 14 | healthcheck: 15 | test: [ "CMD-SHELL", "pg_isready" ] 16 | interval: 10s 17 | timeout: 5s 18 | retries: 5 19 | pgadmin: 20 | container_name: coolstack_pgadmin 21 | image: dpage/pgadmin4 22 | restart: always 23 | ports: 24 | - "${PGADMIN_PORT:-5050}:80" 25 | environment: 26 | PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org} 27 | PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} 28 | PGADMIN_CONFIG_SERVER_MODE: 'False' 29 | networks: 30 | - postgres 31 | depends_on: [db] 32 | 33 | networks: 34 | postgres: 35 | driver: bridge 36 | 37 | volumes: 38 | db: 39 | pgadmin: 40 | -------------------------------------------------------------------------------- /api/extensions/displays/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdsoftpl/cool-stack/bed7a407022d217ff818857c1263a4ba5f9d2b5c/api/extensions/displays/.gitkeep -------------------------------------------------------------------------------- /api/extensions/endpoints/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdsoftpl/cool-stack/bed7a407022d217ff818857c1263a4ba5f9d2b5c/api/extensions/endpoints/.gitkeep -------------------------------------------------------------------------------- /api/extensions/hooks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdsoftpl/cool-stack/bed7a407022d217ff818857c1263a4ba5f9d2b5c/api/extensions/hooks/.gitkeep -------------------------------------------------------------------------------- /api/extensions/interfaces/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdsoftpl/cool-stack/bed7a407022d217ff818857c1263a4ba5f9d2b5c/api/extensions/interfaces/.gitkeep -------------------------------------------------------------------------------- /api/extensions/layouts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdsoftpl/cool-stack/bed7a407022d217ff818857c1263a4ba5f9d2b5c/api/extensions/layouts/.gitkeep -------------------------------------------------------------------------------- /api/extensions/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdsoftpl/cool-stack/bed7a407022d217ff818857c1263a4ba5f9d2b5c/api/extensions/migrations/.gitkeep -------------------------------------------------------------------------------- /api/extensions/modules/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdsoftpl/cool-stack/bed7a407022d217ff818857c1263a4ba5f9d2b5c/api/extensions/modules/.gitkeep -------------------------------------------------------------------------------- /api/extensions/operations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdsoftpl/cool-stack/bed7a407022d217ff818857c1263a4ba5f9d2b5c/api/extensions/operations/.gitkeep -------------------------------------------------------------------------------- /api/extensions/panels/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdsoftpl/cool-stack/bed7a407022d217ff818857c1263a4ba5f9d2b5c/api/extensions/panels/.gitkeep -------------------------------------------------------------------------------- /api/knexfile.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config({path: ".env"}); 2 | 3 | module.exports = { 4 | development: { 5 | client: process.env.DB_CLIENT, 6 | connection: { 7 | host: process.env.DB_HOST, 8 | port: process.env.DB_PORT, 9 | database: process.env.DB_DATABASE, 10 | user: process.env.DB_USER, 11 | password: process.env.DB_PASSWORD 12 | }, 13 | seeds: { 14 | directory: "./seeds/build/seeds" 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cool-stack/api", 3 | "version": "1.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "main": "./types/index.ts", 7 | "types": "./types/index.ts", 8 | "scripts": { 9 | "start": "directus start", 10 | "dev": "cross-env-shell CONFIG_PATH=.env sh ./develop.sh", 11 | "start:local:db": "docker compose -f docker-compose.local.yml up -d", 12 | "stop:local:db": "docker rm $(docker stop coolstack_postgres coolstack_pgadmin)", 13 | "seed:local:db": "rimraf seeds/build && tsc seeds/*.ts --outDir seeds/build --skipLibCheck && knex seed:run", 14 | "get:schema": "cross-env CONFIG_PATH=.env directus schema snapshot --yes ./schema.yaml", 15 | "lint": "cross-env TIMING=1 eslint 'types/**/*.{js,jsx,ts,tsx}'" 16 | }, 17 | "dependencies": { 18 | "directus": "9.26.0", 19 | "pg": "8.12.0" 20 | }, 21 | "devDependencies": { 22 | "@typescript-eslint/eslint-plugin": "5.62.0", 23 | "@typescript-eslint/parser": "5.62.0", 24 | "cross-env": "7.0.3", 25 | "dotenv": "16.4.5", 26 | "eslint": "8.57.0", 27 | "eslint-config-prettier": "8.10.0", 28 | "eslint-plugin-prettier": "4.2.1", 29 | "eslint-plugin-unicorn": "45.0.2", 30 | "open-cli": "7.2.0", 31 | "prettier": "2.8.8", 32 | "rimraf": "3.0.2", 33 | "typescript": "4.9.5" 34 | }, 35 | "engines": { 36 | "node": ">=16.0.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /api/schema.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | directus: 9.23.1 3 | vendor: postgres 4 | collections: 5 | - collection: articles 6 | meta: 7 | accountability: all 8 | archive_app_filter: true 9 | archive_field: status 10 | archive_value: archived 11 | collapse: open 12 | collection: articles 13 | color: null 14 | display_template: null 15 | group: null 16 | hidden: false 17 | icon: article 18 | item_duplication_fields: null 19 | note: null 20 | singleton: false 21 | sort: null 22 | sort_field: null 23 | translations: null 24 | unarchive_value: draft 25 | schema: 26 | name: articles 27 | fields: 28 | - collection: articles 29 | field: author 30 | type: uuid 31 | meta: 32 | collection: articles 33 | conditions: null 34 | display: null 35 | display_options: null 36 | field: author 37 | group: null 38 | hidden: false 39 | interface: select-dropdown-m2o 40 | note: null 41 | options: 42 | enableCreate: false 43 | readonly: false 44 | required: false 45 | sort: null 46 | special: 47 | - m2o 48 | translations: null 49 | validation: null 50 | validation_message: null 51 | width: full 52 | schema: 53 | name: author 54 | table: articles 55 | data_type: uuid 56 | default_value: null 57 | max_length: null 58 | numeric_precision: null 59 | numeric_scale: null 60 | is_nullable: false 61 | is_unique: false 62 | is_primary_key: false 63 | is_generated: false 64 | generation_expression: null 65 | has_auto_increment: false 66 | foreign_key_table: directus_users 67 | foreign_key_column: id 68 | - collection: articles 69 | field: content 70 | type: text 71 | meta: 72 | collection: articles 73 | conditions: null 74 | display: null 75 | display_options: null 76 | field: content 77 | group: null 78 | hidden: false 79 | interface: input-rich-text-html 80 | note: null 81 | options: 82 | toolbar: 83 | - aligncenter 84 | - alignjustify 85 | - alignleft 86 | - alignnone 87 | - alignright 88 | - backcolor 89 | - blockquote 90 | - bold 91 | - bullist 92 | - code 93 | - copy 94 | - customImage 95 | - customLink 96 | - customMedia 97 | - cut 98 | - fontselect 99 | - fontsizeselect 100 | - forecolor 101 | - fullscreen 102 | - h1 103 | - h2 104 | - h3 105 | - h4 106 | - h5 107 | - h6 108 | - hr 109 | - indent 110 | - italic 111 | - ltr rtl 112 | - numlist 113 | - outdent 114 | - paste 115 | - redo 116 | - remove 117 | - removeformat 118 | - selectall 119 | - strikethrough 120 | - subscript 121 | - superscript 122 | - table 123 | - underline 124 | - undo 125 | - unlink 126 | - visualaid 127 | readonly: false 128 | required: false 129 | sort: null 130 | special: null 131 | translations: null 132 | validation: null 133 | validation_message: null 134 | width: full 135 | schema: 136 | name: content 137 | table: articles 138 | data_type: text 139 | default_value: null 140 | max_length: null 141 | numeric_precision: null 142 | numeric_scale: null 143 | is_nullable: false 144 | is_unique: false 145 | is_primary_key: false 146 | is_generated: false 147 | generation_expression: null 148 | has_auto_increment: false 149 | foreign_key_table: null 150 | foreign_key_column: null 151 | - collection: articles 152 | field: date_created 153 | type: timestamp 154 | meta: 155 | collection: articles 156 | conditions: null 157 | display: datetime 158 | display_options: 159 | relative: true 160 | field: date_created 161 | group: null 162 | hidden: true 163 | interface: datetime 164 | note: null 165 | options: null 166 | readonly: true 167 | required: false 168 | sort: null 169 | special: 170 | - date-created 171 | translations: null 172 | validation: null 173 | validation_message: null 174 | width: half 175 | schema: 176 | name: date_created 177 | table: articles 178 | data_type: timestamp with time zone 179 | default_value: null 180 | max_length: null 181 | numeric_precision: null 182 | numeric_scale: null 183 | is_nullable: false 184 | is_unique: false 185 | is_primary_key: false 186 | is_generated: false 187 | generation_expression: null 188 | has_auto_increment: false 189 | foreign_key_table: null 190 | foreign_key_column: null 191 | - collection: articles 192 | field: date_updated 193 | type: timestamp 194 | meta: 195 | collection: articles 196 | conditions: null 197 | display: datetime 198 | display_options: 199 | relative: true 200 | field: date_updated 201 | group: null 202 | hidden: true 203 | interface: datetime 204 | note: null 205 | options: null 206 | readonly: true 207 | required: false 208 | sort: null 209 | special: 210 | - date-updated 211 | translations: null 212 | validation: null 213 | validation_message: null 214 | width: half 215 | schema: 216 | name: date_updated 217 | table: articles 218 | data_type: timestamp with time zone 219 | default_value: null 220 | max_length: null 221 | numeric_precision: null 222 | numeric_scale: null 223 | is_nullable: true 224 | is_unique: false 225 | is_primary_key: false 226 | is_generated: false 227 | generation_expression: null 228 | has_auto_increment: false 229 | foreign_key_table: null 230 | foreign_key_column: null 231 | - collection: articles 232 | field: id 233 | type: integer 234 | meta: 235 | collection: articles 236 | conditions: null 237 | display: null 238 | display_options: null 239 | field: id 240 | group: null 241 | hidden: true 242 | interface: input 243 | note: null 244 | options: null 245 | readonly: true 246 | required: false 247 | sort: null 248 | special: null 249 | translations: null 250 | validation: null 251 | validation_message: null 252 | width: full 253 | schema: 254 | name: id 255 | table: articles 256 | data_type: integer 257 | default_value: nextval('articles_id_seq'::regclass) 258 | max_length: null 259 | numeric_precision: 32 260 | numeric_scale: 0 261 | is_nullable: false 262 | is_unique: true 263 | is_primary_key: true 264 | is_generated: false 265 | generation_expression: null 266 | has_auto_increment: true 267 | foreign_key_table: null 268 | foreign_key_column: null 269 | - collection: articles 270 | field: slug 271 | type: string 272 | meta: 273 | collection: articles 274 | conditions: null 275 | display: null 276 | display_options: null 277 | field: slug 278 | group: null 279 | hidden: false 280 | interface: input 281 | note: null 282 | options: 283 | slug: true 284 | readonly: false 285 | required: false 286 | sort: null 287 | special: null 288 | translations: null 289 | validation: null 290 | validation_message: null 291 | width: full 292 | schema: 293 | name: slug 294 | table: articles 295 | data_type: character varying 296 | default_value: null 297 | max_length: 255 298 | numeric_precision: null 299 | numeric_scale: null 300 | is_nullable: true 301 | is_unique: true 302 | is_primary_key: false 303 | is_generated: false 304 | generation_expression: null 305 | has_auto_increment: false 306 | foreign_key_table: null 307 | foreign_key_column: null 308 | - collection: articles 309 | field: status 310 | type: string 311 | meta: 312 | collection: articles 313 | conditions: null 314 | display: labels 315 | display_options: 316 | choices: 317 | - text: $t:published 318 | value: published 319 | foreground: '#FFFFFF' 320 | background: var(--primary) 321 | - text: $t:draft 322 | value: draft 323 | foreground: '#18222F' 324 | background: '#D3DAE4' 325 | - text: $t:archived 326 | value: archived 327 | foreground: '#FFFFFF' 328 | background: var(--warning) 329 | showAsDot: true 330 | field: status 331 | group: null 332 | hidden: false 333 | interface: select-dropdown 334 | note: null 335 | options: 336 | choices: 337 | - text: $t:published 338 | value: published 339 | - text: $t:draft 340 | value: draft 341 | - text: $t:archived 342 | value: archived 343 | readonly: false 344 | required: false 345 | sort: null 346 | special: null 347 | translations: null 348 | validation: null 349 | validation_message: null 350 | width: full 351 | schema: 352 | name: status 353 | table: articles 354 | data_type: character varying 355 | default_value: draft 356 | max_length: 255 357 | numeric_precision: null 358 | numeric_scale: null 359 | is_nullable: false 360 | is_unique: false 361 | is_primary_key: false 362 | is_generated: false 363 | generation_expression: null 364 | has_auto_increment: false 365 | foreign_key_table: null 366 | foreign_key_column: null 367 | - collection: articles 368 | field: title 369 | type: string 370 | meta: 371 | collection: articles 372 | conditions: null 373 | display: null 374 | display_options: null 375 | field: title 376 | group: null 377 | hidden: false 378 | interface: input 379 | note: null 380 | options: null 381 | readonly: false 382 | required: false 383 | sort: null 384 | special: null 385 | translations: null 386 | validation: null 387 | validation_message: null 388 | width: full 389 | schema: 390 | name: title 391 | table: articles 392 | data_type: character varying 393 | default_value: null 394 | max_length: 255 395 | numeric_precision: null 396 | numeric_scale: null 397 | is_nullable: false 398 | is_unique: false 399 | is_primary_key: false 400 | is_generated: false 401 | generation_expression: null 402 | has_auto_increment: false 403 | foreign_key_table: null 404 | foreign_key_column: null 405 | relations: 406 | - collection: articles 407 | field: author 408 | related_collection: directus_users 409 | meta: 410 | junction_field: null 411 | many_collection: articles 412 | many_field: author 413 | one_allowed_collections: null 414 | one_collection: directus_users 415 | one_collection_field: null 416 | one_deselect_action: nullify 417 | one_field: null 418 | sort_field: null 419 | schema: 420 | table: articles 421 | column: author 422 | foreign_key_table: directus_users 423 | foreign_key_column: id 424 | constraint_name: articles_author_foreign 425 | on_update: NO ACTION 426 | on_delete: NO ACTION 427 | -------------------------------------------------------------------------------- /api/seeds/00-developmentToken.ts: -------------------------------------------------------------------------------- 1 | import type {Knex} from "knex"; 2 | 3 | /** 4 | * This way we handle persistent static token for development purposes. 5 | * The token is being granted for the admin user! 6 | * On production, collection access must be set for the given user/role. 7 | */ 8 | export async function seed(knex: Knex): Promise { 9 | await knex("directus_users") 10 | .where({email: process.env.ADMIN_EMAIL}) 11 | .update({token: process.env.DEVELOPMENT_TOKEN}); 12 | } 13 | -------------------------------------------------------------------------------- /api/seeds/articles.ts: -------------------------------------------------------------------------------- 1 | import {Collection, Status} from "../types"; 2 | import type {Knex} from "knex"; 3 | import type {Article} from "../types"; 4 | 5 | export async function seed(knex: Knex): Promise { 6 | await knex(Collection.Articles).del(); 7 | 8 | const user = await knex(Collection.Users).where({email: process.env.ADMIN_EMAIL}).first(); 9 | const articles: Array
= [ 10 | { 11 | id: 1, 12 | status: Status.Published, 13 | date_created: "2023-01-01T12:00:00", 14 | date_updated: null, 15 | author: user.id, 16 | slug: "remix-vs-next", 17 | title: "Remix vs Next.js", 18 | content: `

TL;DR

  • Remix is as fast or faster than Next.js at serving static content
  • Remix is faster than Next.js at serving dynamic content
  • Remix enables fast user experiences even on slow networks
  • Remix automatically handles errors, interruptions, and race conditions, Next.js doesn't
  • Next.js encourages client side JavaScript for serving dynamic content, Remix doesn't
  • Next.js requires client side JavaScript for data mutations, Remix doesn't
  • Next.js build times increase linearly with your data, Remix build times are nearly instant and decoupled from data
  • Next.js requires you to change your application architecture and sacrifice performance when your data scales
  • We think Remix's abstractions lead to better application code

Read full article

` 19 | }, 20 | { 21 | id: 2, 22 | status: Status.Published, 23 | date_created: "2023-01-01T12:00:00", 24 | date_updated: null, 25 | author: user.id, 26 | slug: "everything-you-need-to-know-about-directus-roles", 27 | title: "Everything you need to know about Directus Roles", 28 | content: `

In Directus, there is a User Management module where you can add new users to your platform. By default, Directus only has 2 roles available for these users, Public (no access) and Adminisrator (full access). In this article I will cover how you can create and use Roles to build a successful team and secure your application.

Roles are a core feature of Directus that controls how your users interact with the data. It can be as simple as read and write access or as complex as specific fields within a collection. It's good practice to create a new role for each new user or team and limit their access to what they need. You can always grant access when something else is required.

Read full article

` 29 | }, 30 | { 31 | id: 3, 32 | status: Status.Published, 33 | date_created: "2023-01-01T12:00:00", 34 | date_updated: null, 35 | author: user.id, 36 | slug: "how-to-backup-directus", 37 | title: "How to backup Directus", 38 | content: `

To backup Directus, make a copy of your project files, .env file and perform a data dump of your database.

Backing-up a Project

It is important to make a backup of your project in case everything goes very badly. Some updates will make changes to your database which will making rolling back to previous versions impossible.

  1. Make a copy of the files within each storage adapter, and store them in a safe place. This can be a zip/tar file somewhere on the same server so you can easily extract them again if needed. If space is an issue, the backup can be stored elsewhere once you are happy the update has succeeded.
  2. Make a copy of the Env file (/project_folder/.env), and store it in a safe place. This contains all your configurations.
  3. Create a database dump (a sql backup of your database)

Read full article

` 39 | } 40 | ]; 41 | 42 | await knex(Collection.Articles).insert(articles); 43 | } 44 | -------------------------------------------------------------------------------- /api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cool-stack/configs/tsconfig/base.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "allowJs": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "lib": ["DOM", "DOM.Iterable", "ES2019"], 8 | "strict": true, 9 | "target": "ES2019", 10 | "esModuleInterop": true, 11 | "isolatedModules": true, 12 | "moduleResolution": "node", 13 | "noEmit": true, 14 | "resolveJsonModule": true, 15 | }, 16 | "include": ["extensions/**/*.ts", "seeds/**/*.ts", "types/**/*.ts"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /api/types/Articles.ts: -------------------------------------------------------------------------------- 1 | import type {User} from "./Users"; 2 | import {isStatus, Status} from "./Status"; 3 | 4 | export interface Article { 5 | id: number; 6 | status: Status; 7 | date_created: string; 8 | date_updated: null | string; 9 | author: string | User; 10 | slug: string; 11 | title: string; 12 | content: string; 13 | } 14 | 15 | export function isArticle(article: any): article is Article { 16 | return !!( 17 | article && 18 | typeof article === "object" && 19 | typeof article.id === "number" && 20 | isStatus(article.status) && 21 | typeof article.date_created === "string" && 22 | (typeof article.date_updated === "object" || typeof article.date_updated === "string") && 23 | typeof article.slug === "string" && 24 | typeof article.title === "string" && 25 | typeof article.content === "string" && 26 | //relational fields 27 | "author" in article 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /api/types/Status.ts: -------------------------------------------------------------------------------- 1 | export enum Status { 2 | Draft = "draft", 3 | Published = "published", 4 | Archived = "archived" 5 | } 6 | 7 | export function isStatus(status: any): status is Status { 8 | return !!( 9 | status && 10 | typeof status === "string" && 11 | Object.values(Status).includes(status as Status) 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /api/types/Users.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | id: string; 3 | email: string; 4 | } 5 | 6 | export function isUser(user: any): user is User { 7 | return !!( 8 | user && 9 | typeof user === "object" && 10 | typeof user.id === "string" && 11 | typeof user.email === "string" 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /api/types/index.ts: -------------------------------------------------------------------------------- 1 | import type {Article} from "./Articles"; 2 | import type {User} from "./Users"; 3 | 4 | export * from "./Articles"; 5 | export * from "./Users"; 6 | export * from "./Status"; 7 | 8 | export enum Collection { 9 | Articles = "articles", 10 | Users = "directus_users" 11 | } 12 | 13 | export type Collections = { 14 | [Collection.Articles]: Article; 15 | [Collection.Users]: User; 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cool-stack", 3 | "description": "Ice-cool 🧊 Remix + Directus starter template.", 4 | "version": "1.0.0", 5 | "repository": "https://github.com/tdsoftpl/cool-stack.git", 6 | "private": true, 7 | "license": "MIT", 8 | "workspaces": [ 9 | "api", 10 | "packages/*", 11 | "page" 12 | ], 13 | "scripts": { 14 | "bootstrap": "yarn && turbo run generate:css && turbo run build", 15 | "dev": "turbo run dev --parallel", 16 | "build": "turbo run build", 17 | "clean": "turbo run clean", 18 | "lint": "turbo run lint", 19 | "format": "prettier --write \"**/*.{js,ts,tsx,md}\"", 20 | "test": "turbo run test --filter=*/page --filter=*/ui --no-cache --only", 21 | "graph": "turbo run build --graph" 22 | }, 23 | "devDependencies": { 24 | "@tdsoft/prettier-config": "^1.0.2", 25 | "prettier": "2.8.8", 26 | "turbo": "latest" 27 | }, 28 | "resolutions": { 29 | "@types/react": "18.3.5", 30 | "@types/react-dom": "18.3.0", 31 | "react": "18.3.1", 32 | "react-dom": "18.3.1" 33 | }, 34 | "engines": { 35 | "npm": ">=7.0.0", 36 | "node": ">=14" 37 | }, 38 | "packageManager": "yarn@1.22.22" 39 | } 40 | -------------------------------------------------------------------------------- /packages/configs/jest/jest-common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | resetMocks: true, 4 | passWithNoTests: true, 5 | testEnvironment: "jsdom", 6 | setupFilesAfterEnv: ["@testing-library/jest-dom"], 7 | transform: { 8 | "\\.m?js$": "babel-jest" 9 | }, 10 | moduleDirectories: ["node_modules"], 11 | moduleFileExtensions: ["js", "jsx", "json", "ts", "tsx"] 12 | }; 13 | -------------------------------------------------------------------------------- /packages/configs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cool-stack/configs", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "files": [ 6 | "./tsconfig/base.json", 7 | "./tsconfig/react-library.json", 8 | "./jest/jest-common.js", 9 | "./tailwind/tailwind.config.js" 10 | ], 11 | "peerDependencies": { 12 | "@tailwindcss/typography": "^0.5.9", 13 | "tailwindcss": "^3.2.4" 14 | }, 15 | "devDependencies": { 16 | "@tailwindcss/typography": "0.5.15", 17 | "tailwindcss": "3.4.10" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/configs/tailwind/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const tailwindTypography = require("@tailwindcss/typography"); 2 | 3 | module.exports = { 4 | theme: {}, 5 | plugins: [tailwindTypography] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/configs/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "inlineSources": false, 11 | "isolatedModules": true, 12 | "moduleResolution": "node", 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "preserveWatchOutput": true, 16 | "skipLibCheck": true, 17 | "strict": true 18 | }, 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/configs/tsconfig/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "lib": ["ES2015", "dom"], 7 | "module": "ESNext", 8 | "target": "ES6", 9 | "jsx": "react-jsx" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/ui/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["@typescript-eslint", "prettier"], 3 | extends: [ 4 | "eslint:recommended", 5 | "plugin:prettier/recommended", 6 | "plugin:import/recommended", 7 | "plugin:unicorn/recommended", 8 | "plugin:jest-dom/recommended", 9 | "plugin:react/recommended", 10 | "plugin:react-hooks/recommended" 11 | ], 12 | env: { 13 | browser: true, 14 | node: true, 15 | es2022: true, 16 | jest: true 17 | }, 18 | ignorePatterns: ["**/*.js"], 19 | parser: "@typescript-eslint/parser", 20 | parserOptions: { 21 | project: "tsconfig.json", 22 | tsconfigRootDir: __dirname, 23 | sourceType: "module" 24 | }, 25 | settings: { 26 | react: { 27 | version: "detect" 28 | }, 29 | "import/resolver": { 30 | node: { 31 | extensions: [".ts", ".tsx"], 32 | moduleDirectory: ["src", "node_modules"] 33 | } 34 | } 35 | }, 36 | rules: { 37 | "no-unused-vars": "off", 38 | "@typescript-eslint/no-unused-vars": "warn", 39 | "unicorn/text-encoding-identifier-case": "off", 40 | "unicorn/filename-case": "off", 41 | "unicorn/no-null": "off", 42 | "unicorn/no-nested-ternary": "off", 43 | "unicorn/no-array-callback-reference": "off", 44 | "unicorn/prevent-abbreviations": [ 45 | "warn", 46 | { 47 | replacements: { 48 | args: false, 49 | props: false, 50 | ref: false 51 | } 52 | } 53 | ] 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /packages/ui/.gitignore: -------------------------------------------------------------------------------- 1 | src/style.css 2 | -------------------------------------------------------------------------------- /packages/ui/.prettierrc: -------------------------------------------------------------------------------- 1 | "@tdsoft/prettier-config" 2 | -------------------------------------------------------------------------------- /packages/ui/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], 3 | addons: [ 4 | "@storybook/addon-links", 5 | "@storybook/addon-essentials", 6 | "@storybook/addon-viewport", 7 | "@storybook/addon-interactions", 8 | "@storybook/addon-a11y", 9 | "@storybook/addon-storysource", 10 | "storybook-addon-designs", 11 | { 12 | name: "@storybook/addon-postcss", 13 | options: { 14 | postcssLoaderOptions: { 15 | implementation: require("postcss") 16 | } 17 | } 18 | } 19 | ], 20 | framework: "@storybook/react" 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ui/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import "../src/style.css"; 2 | 3 | export const parameters = { 4 | actions: {argTypesRegex: "^on[A-Z].*"}, 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/ 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /packages/ui/babel.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Babel is used only in the test environment for transforming packages from esm to cjs. 3 | */ 4 | module.exports = { 5 | env: { 6 | test: { 7 | plugins: ["@babel/plugin-transform-modules-commonjs"] 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /packages/ui/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require("@cool-stack/configs/jest/jest-common"), 3 | rootDir: "./", 4 | /** 5 | * Jest doesn't transform node_modules by default, 6 | * so one needs to modify transformIgnorePatterns 7 | * and include all modules to be transformed from ESM to CJS. 8 | */ 9 | transformIgnorePatterns: [] 10 | }; 11 | -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cool-stack/ui", 3 | "version": "1.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "main": "./src/index.ts", 7 | "types": "./src/index.ts", 8 | "scripts": { 9 | "dev": "concurrently \"yarn run dev:storybook\" \"yarn run dev:css\"", 10 | "dev:storybook": "start-storybook --quiet -p 6006", 11 | "dev:css": "yarn generate:css --watch", 12 | "build:storybook": "build-storybook", 13 | "generate:css": "tailwindcss -i ./src/global.css -o ./src/style.css", 14 | "lint": "cross-env TIMING=1 eslint 'src/**/*.{js,jsx,ts,tsx}'", 15 | "test": "jest" 16 | }, 17 | "peerDependencies": { 18 | "clsx": "^1.2.1", 19 | "react": "18.3.1" 20 | }, 21 | "dependencies": { 22 | "clsx": "1.2.1", 23 | "react": "18.3.0" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "7.25.2", 27 | "@babel/plugin-transform-modules-commonjs": "7.24.8", 28 | "@cool-stack/configs": "*", 29 | "@storybook/addon-a11y": "6.5.16", 30 | "@storybook/addon-actions": "6.5.16", 31 | "@storybook/addon-essentials": "6.5.16", 32 | "@storybook/addon-interactions": "6.5.16", 33 | "@storybook/addon-links": "6.5.16", 34 | "@storybook/addon-postcss": "2.0.0", 35 | "@storybook/addon-storysource": "6.5.16", 36 | "@storybook/addon-viewport": "6.5.16", 37 | "@storybook/react": "6.5.16", 38 | "@testing-library/jest-dom": "5.17.0", 39 | "@testing-library/react": "13.4.0", 40 | "@types/jest": "29.5.12", 41 | "@types/react": "18.2.79", 42 | "@typescript-eslint/eslint-plugin": "5.62.0", 43 | "@typescript-eslint/parser": "5.62.0", 44 | "babel-jest": "29.7.0", 45 | "babel-loader": "9.1.3", 46 | "concurrently": "7.6.0", 47 | "eslint": "8.57.0", 48 | "eslint-config-prettier": "8.10.0", 49 | "eslint-plugin-import": "2.30.0", 50 | "eslint-plugin-jest-dom": "4.0.3", 51 | "eslint-plugin-prettier": "4.2.1", 52 | "eslint-plugin-react": "7.35.2", 53 | "eslint-plugin-react-hooks": "4.6.2", 54 | "eslint-plugin-unicorn": "45.0.2", 55 | "jest": "29.7.0", 56 | "jest-environment-jsdom": "29.7.0", 57 | "postcss": "8.4.38", 58 | "prettier": "2.8.8", 59 | "prettier-plugin-tailwindcss": "0.6.6", 60 | "storybook-addon-designs": "6.3.1", 61 | "tailwindcss": "3.4.10", 62 | "ts-jest": "29.2.5", 63 | "typescript": "4.9.5" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /packages/ui/src/Avatar/Avatar.mocks.ts: -------------------------------------------------------------------------------- 1 | import {AvatarProps} from "./Avatar.props"; 2 | 3 | export const avatarPropsMock: AvatarProps = { 4 | src: "https://avatars.githubusercontent.com/u/42771191?s=200&v=4", 5 | nickname: "John Doe" 6 | }; 7 | -------------------------------------------------------------------------------- /packages/ui/src/Avatar/Avatar.props.ts: -------------------------------------------------------------------------------- 1 | export interface AvatarProps { 2 | nickname: string; 3 | src?: string; 4 | } 5 | -------------------------------------------------------------------------------- /packages/ui/src/Avatar/Avatar.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import type {ComponentStory, ComponentMeta} from "@storybook/react"; 3 | import Avatar from "./Avatar"; 4 | import {avatarPropsMock} from "./Avatar.mocks"; 5 | 6 | export default { 7 | title: "Avatar", 8 | component: Avatar 9 | } as ComponentMeta; 10 | 11 | const Template: ComponentStory = (args) => ; 12 | 13 | export const Default = Template.bind({}); 14 | Default.args = avatarPropsMock; 15 | 16 | export const NoImageSource = Template.bind({}); 17 | NoImageSource.args = {...avatarPropsMock, src: undefined}; 18 | -------------------------------------------------------------------------------- /packages/ui/src/Avatar/Avatar.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {screen, render} from "@testing-library/react"; 3 | 4 | import Avatar from "./Avatar"; 5 | import {avatarPropsMock} from "./Avatar.mocks"; 6 | 7 | describe(Avatar.name, () => { 8 | it("should render the user's initials", () => { 9 | render(); 10 | expect(screen.getByText("JD")).toBeInTheDocument(); 11 | }); 12 | 13 | it("should render provided avatar image", () => { 14 | const {container} = render(); 15 | const image = container.querySelector("img"); 16 | expect(image).toBeInTheDocument(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/ui/src/Avatar/Avatar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {AvatarProps} from "./Avatar.props"; 3 | import {generateInitials} from "./Avatar.utils"; 4 | 5 | const Avatar: React.FC = ({nickname, src}) => { 6 | return src ? ( 7 |
8 | {`${nickname} 14 |
15 | ) : ( 16 |
17 | 18 | {generateInitials(nickname)} 19 | 20 |
21 | ); 22 | }; 23 | 24 | export default Avatar; 25 | -------------------------------------------------------------------------------- /packages/ui/src/Avatar/Avatar.utils.ts: -------------------------------------------------------------------------------- 1 | export const generateInitials = (nickname: string): string => { 2 | const nicknameSplit = nickname.trim().split(" "); 3 | 4 | switch (nicknameSplit.length) { 5 | case 1: { 6 | return nicknameSplit[0].slice(0, 2).toUpperCase(); 7 | } 8 | case 2: { 9 | return `${nicknameSplit[0][0]}${nicknameSplit[1][0]}`.toUpperCase(); 10 | } 11 | default: { 12 | return `${nicknameSplit[0][0]}${ 13 | nicknameSplit[nicknameSplit.length - 1][0] 14 | }`.toUpperCase(); 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /packages/ui/src/Avatar/index.ts: -------------------------------------------------------------------------------- 1 | export {default as Avatar} from "./Avatar"; 2 | -------------------------------------------------------------------------------- /packages/ui/src/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /packages/ui/src/index.ts: -------------------------------------------------------------------------------- 1 | /* Components */ 2 | export * from "./Avatar"; 3 | -------------------------------------------------------------------------------- /packages/ui/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require("@cool-stack/configs/tailwind/tailwind.config.js")], 3 | content: ["./src/**/*.{ts,tsx,mdx}"] 4 | }; 5 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cool-stack/configs/tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /page/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV="development" 2 | REMIX_ENV="development" 3 | API_URL="http://localhost:8055" 4 | API_TOKEN="token" 5 | -------------------------------------------------------------------------------- /page/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV="development" 2 | REMIX_ENV="development" 3 | API_URL="http://localhost:8055" 4 | API_TOKEN="token" 5 | -------------------------------------------------------------------------------- /page/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["prettier", "@typescript-eslint"], 3 | extends: [ 4 | "eslint:recommended", 5 | "@remix-run/eslint-config", 6 | "@remix-run/eslint-config/node", 7 | "plugin:prettier/recommended", 8 | "plugin:import/recommended", 9 | "plugin:unicorn/recommended", 10 | "plugin:jest-dom/recommended", 11 | "plugin:react/recommended", 12 | "plugin:react-hooks/recommended" 13 | ], 14 | env: { 15 | node: true 16 | }, 17 | ignorePatterns: ["**/*.js"], 18 | parser: "@typescript-eslint/parser", 19 | parserOptions: { 20 | project: "tsconfig.json", 21 | tsconfigRootDir: __dirname, 22 | sourceType: "module" 23 | }, 24 | settings: { 25 | react: { 26 | version: "detect" 27 | }, 28 | "import/resolver": { 29 | node: { 30 | extensions: [".ts", ".tsx"], 31 | moduleDirectory: ["app", "node_modules"] 32 | } 33 | } 34 | }, 35 | rules: { 36 | "no-unused-vars": "off", 37 | "@typescript-eslint/no-unused-vars": "warn", 38 | "react/prop-types": "off", 39 | "jsx-a11y/anchor-has-content": "off", 40 | "jsx-a11y/img-redundant-alt": "off", 41 | "unicorn/text-encoding-identifier-case": "off", 42 | "unicorn/filename-case": "off", 43 | "unicorn/no-null": "off", 44 | "unicorn/no-nested-ternary": "off", 45 | "unicorn/no-array-callback-reference": "off", 46 | "unicorn/prevent-abbreviations": [ 47 | "warn", 48 | { 49 | replacements: { 50 | args: false, 51 | props: false, 52 | ref: false 53 | } 54 | } 55 | ] 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /page/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /server/index.js 5 | /server/index.js.map 6 | /public/build 7 | preferences.arc 8 | sam.json 9 | sam.yaml 10 | .env 11 | 12 | app/styles/* 13 | -------------------------------------------------------------------------------- /page/.prettierrc: -------------------------------------------------------------------------------- 1 | "@tdsoft/prettier-config" 2 | -------------------------------------------------------------------------------- /page/app.arc: -------------------------------------------------------------------------------- 1 | @app 2 | cool-stack-page 3 | 4 | @http 5 | /* 6 | method any 7 | src server 8 | 9 | @static 10 | 11 | @aws 12 | profile default 13 | region us-east-1 14 | -------------------------------------------------------------------------------- /page/app/api/index.ts: -------------------------------------------------------------------------------- 1 | import {Directus} from "@directus/sdk"; 2 | import type {Collections} from "@cool-stack/api"; 3 | 4 | export const $api = new Directus(process.env.API_URL!, { 5 | auth: {staticToken: process.env.API_TOKEN!} 6 | }); 7 | -------------------------------------------------------------------------------- /page/app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {RemixBrowser} from "@remix-run/react"; 3 | import {hydrateRoot} from "react-dom/client"; 4 | 5 | hydrateRoot(document, ); 6 | -------------------------------------------------------------------------------- /page/app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import type {EntryContext} from "@remix-run/node"; 3 | import {RemixServer} from "@remix-run/react"; 4 | import {renderToString} from "react-dom/server"; 5 | 6 | export default function handleRequest( 7 | request: Request, 8 | responseStatusCode: number, 9 | responseHeaders: Headers, 10 | remixContext: EntryContext 11 | ) { 12 | const markup = renderToString(); 13 | 14 | responseHeaders.set("Content-Type", "text/html"); 15 | 16 | return new Response("" + markup, { 17 | status: responseStatusCode, 18 | headers: responseHeaders 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /page/app/root.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Links, 4 | LiveReload, 5 | Meta, 6 | Outlet, 7 | Scripts, 8 | ScrollRestoration, 9 | useCatch 10 | } from "@remix-run/react"; 11 | import type {MetaFunction, LinksFunction, ErrorBoundaryComponent} from "@remix-run/node"; 12 | import type {CatchBoundaryComponent} from "@remix-run/react/dist/routeModules"; 13 | 14 | import tailwindStylesheetUrl from "./styles/style.css"; 15 | 16 | export const meta: MetaFunction = () => ({ 17 | charset: "utf-8", 18 | title: "🧊 Cool Stack", 19 | viewport: "width=device-width,initial-scale=1" 20 | }); 21 | 22 | export const links: LinksFunction = () => { 23 | return [ 24 | {rel: "stylesheet", href: tailwindStylesheetUrl}, 25 | {rel: "shortcut icon", href: "/_static/favicon.ico"} 26 | ]; 27 | }; 28 | 29 | const App = () => { 30 | return ( 31 | 32 | 33 | 34 | 35 | 36 | 37 | {/* Hero section */} 38 |
39 |
40 |
41 |
42 |
43 |
44 | Cool-Stack Cover Image 49 |
50 |
51 |
52 |

53 | Cool-Stack 🧊 54 | 55 | template repository 56 | 57 |

58 |

59 | Develop your next page using{" "} 60 | 66 | Remix.run 67 | 68 | and 69 | 75 | Directus.io 76 | 77 |

78 |
79 |
80 |
81 |
82 |
83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | ); 91 | }; 92 | 93 | export const CatchBoundary: CatchBoundaryComponent = () => { 94 | const caught = useCatch(); 95 | 96 | return ( 97 | 98 | 99 | Oops! 100 | 101 | 102 | 103 | 104 | 105 | CatchBoundary: {caught.data} 106 | 107 | 108 | 109 | 110 | ); 111 | }; 112 | 113 | export const ErrorBoundary: ErrorBoundaryComponent = ({error}) => { 114 | return ( 115 | 116 | 117 | Oh no! Something went wrong... 118 | 119 | 120 | 121 | 122 | 123 | 124 | ErrorBoundary: {error.toString()} 125 | 126 | 127 | 128 | 129 | ); 130 | }; 131 | 132 | export default App; 133 | -------------------------------------------------------------------------------- /page/app/routes/blog/$slug.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import invariant from "tiny-invariant"; 3 | import {useLoaderData} from "@remix-run/react"; 4 | import {json} from "@remix-run/node"; 5 | import type {LoaderFunction} from "@remix-run/node"; 6 | import {Collection, isArticle, isUser, Status} from "@cool-stack/api"; 7 | import type {Article, User} from "@cool-stack/api"; 8 | import {Avatar} from "@cool-stack/ui"; 9 | import {$api} from "~/api"; 10 | 11 | interface LoaderData { 12 | article: Article & {author: User}; 13 | } 14 | 15 | export const loader: LoaderFunction = async ({params}) => { 16 | const articleResponse = await $api.items(Collection.Articles).readByQuery({ 17 | fields: ["*", "author.id" as "author", "author.email" as "author"], 18 | filter: { 19 | status: { 20 | _eq: Status.Published 21 | }, 22 | slug: { 23 | _eq: params.slug 24 | } 25 | } 26 | }); 27 | 28 | const article = articleResponse.data?.[0]; 29 | 30 | if (!article) { 31 | throw new Response("Article Not Found", { 32 | status: 404 33 | }); 34 | } 35 | 36 | invariant(isArticle(article) && isUser(article.author), "Error while loading article data"); 37 | 38 | return json({ 39 | article: article as LoaderData["article"] 40 | }); 41 | }; 42 | 43 | export default function ArticlePost() { 44 | const {article} = useLoaderData(); 45 | 46 | return ( 47 |
48 |
49 |
50 |

51 | {new Date(article.date_updated || article.date_created).toLocaleDateString( 52 | "en-US" 53 | )} 54 |

55 |

56 | {article.title} 57 |

58 | 59 |
60 | 61 |
62 | 63 | {article.author.email} 64 | 65 |
66 |
67 |
68 | 69 |
73 |
74 |
75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /page/app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {$api} from "~/api"; 3 | import {Link, useLoaderData} from "@remix-run/react"; 4 | import {json} from "@remix-run/node"; 5 | import {Collection, isArticle} from "@cool-stack/api"; 6 | import type {Article} from "@cool-stack/api"; 7 | import type {LoaderFunction} from "@remix-run/node"; 8 | 9 | interface LoaderData { 10 | articles: Array
; 11 | } 12 | 13 | export const loader: LoaderFunction = async () => { 14 | const articles = await $api.items(Collection.Articles).readByQuery({ 15 | sort: ["-date_created"], 16 | limit: 10 17 | }); 18 | 19 | return json({ 20 | articles: articles.data?.filter(isArticle) ?? [] 21 | }); 22 | }; 23 | 24 | export default function Index() { 25 | const {articles} = useLoaderData(); 26 | 27 | return ( 28 |
29 |
30 |
31 |
32 |
33 |
34 |

35 | Articles from Directus 36 |

37 |

38 | Lorem Ipsum is simply dummy text of the printing and typesetting 39 | industry. Lorem Ipsum has been the industry's standard dummy text 40 | ever since the 1500s, when an unknown printer took a galley of type 41 | and scrambled it to make a type specimen book. It has survived not 42 | only five centuries, but also the leap into electronic typesetting, 43 | remaining essentially unchanged. 44 |

45 |
46 |
47 |
48 |
49 | {articles.map((article) => ( 50 |
51 |

52 | {new Date( 53 | article.date_updated || article.date_created 54 | ).toLocaleDateString("en-US")} 55 |

56 | 57 |

58 | {article.title} 59 |

60 |

61 | Read full 62 |

63 | 64 |
65 | ))} 66 |
67 |
68 |
69 |
70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /page/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from "cypress"; 2 | 3 | export default defineConfig({ 4 | e2e: { 5 | setupNodeEvents: (on, config) => { 6 | const configOverrides: Partial = { 7 | baseUrl: "http://localhost:3333", 8 | video: false, 9 | screenshotOnRunFailure: false 10 | }; 11 | 12 | // To use this: 13 | // cy.task('log', whateverYouWantInTheTerminal) 14 | on("task", { 15 | log: (message) => { 16 | console.log(message); 17 | 18 | return null; 19 | } 20 | }); 21 | 22 | return {...config, ...configOverrides}; 23 | } 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /page/cypress/e2e/smoke.cy.ts: -------------------------------------------------------------------------------- 1 | describe("💨 Smoke tests", () => { 2 | it("should have a local instance of Directus running", () => { 3 | const defaultDirectusUrl = "http://localhost:8055"; 4 | cy.request(`${defaultDirectusUrl}/server/info`).its("isOkStatusCode"); 5 | }); 6 | 7 | it("should visit the page and navigate to the first article", () => { 8 | cy.visit(""); 9 | cy.contains("a > p", "Read full").click(); 10 | cy.url().should("match", /.*blog/); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /page/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /page/cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************** 3 | // This example commands.ts shows you how to 4 | // create various custom commands and overwrite 5 | // existing commands. 6 | // 7 | // For more comprehensive examples of custom 8 | // commands please read more here: 9 | // https://on.cypress.io/custom-commands 10 | // *********************************************** 11 | // 12 | // 13 | // -- This is a parent command -- 14 | // Cypress.Commands.add('login', (email, password) => { ... }) 15 | // 16 | // 17 | // -- This is a child command -- 18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 19 | // 20 | // 21 | // -- This is a dual command -- 22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 23 | // 24 | // 25 | // -- This will overwrite an existing command -- 26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 27 | // 28 | // declare global { 29 | // namespace Cypress { 30 | // interface Chainable { 31 | // login(email: string, password: string): Chainable 32 | // drag(subject: string, options?: Partial): Chainable 33 | // dismiss(subject: string, options?: Partial): Chainable 34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable 35 | // } 36 | // } 37 | // } 38 | -------------------------------------------------------------------------------- /page/cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/prevent-abbreviations */ 2 | // *********************************************************** 3 | // This example support/e2e.ts is processed and 4 | // loaded automatically before your test files. 5 | // 6 | // This is a great place to put global configuration and 7 | // behavior that modifies Cypress. 8 | // 9 | // You can change the location of this file or turn off 10 | // automatically serving support files with the 11 | // 'supportFile' configuration option. 12 | // 13 | // You can read more here: 14 | // https://on.cypress.io/configuration 15 | // *********************************************************** 16 | 17 | import "@testing-library/cypress/add-commands"; 18 | import "./commands"; 19 | 20 | Cypress.on("uncaught:exception", (error) => { 21 | // Cypress and React Hydrating the document don't get along 22 | // for some unknown reason. Hopefully we figure out why eventually 23 | // so we can remove this. 24 | if ( 25 | /hydrat/i.test(error.message) || 26 | /Minified React error #418/.test(error.message) || 27 | /Minified React error #423/.test(error.message) 28 | ) { 29 | return false; 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /page/cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cool-stack/configs/tsconfig/base.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "noEmit": true, 6 | "types": ["node", "cypress", "@testing-library/cypress"], 7 | "esModuleInterop": true, 8 | "jsx": "react-jsx", 9 | "moduleResolution": "node", 10 | "target": "ES2019", 11 | "strict": true, 12 | "skipLibCheck": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": false, 15 | }, 16 | "include": [ 17 | "e2e/**/*", 18 | "support/**/*", 19 | "../node_modules/cypress", 20 | "../node_modules/@testing-library/cypress" 21 | ], 22 | "exclude": [ 23 | "../node_modules/@types/jest", 24 | "../node_modules/@testing-library/jest-dom" 25 | ] 26 | } -------------------------------------------------------------------------------- /page/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require("@cool-stack/configs/jest/jest-common"), 3 | rootDir: "./" 4 | }; 5 | -------------------------------------------------------------------------------- /page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cool-stack/page", 3 | "version": "1.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "sideEffects": false, 7 | "scripts": { 8 | "dev": "concurrently \"yarn run dev:remix\" \"yarn run dev:arc\" \"yarn run dev:css\"", 9 | "dev:remix": "remix build && open-cli http://localhost:3333 && remix watch", 10 | "dev:arc": "arc sandbox", 11 | "dev:css": "yarn generate:css --watch", 12 | "generate:css": "tailwindcss -i ../packages/ui/src/global.css -o ./app/styles/style.css --minify", 13 | "build": "yarn generate:css && remix build", 14 | "clean": "rimraf server/index.js && rimraf public/build", 15 | "lint": "cross-env TIMING=1 eslint 'app/**/*.{js,jsx,ts,tsx}'", 16 | "test": "jest", 17 | "test:e2e:dev": "cypress open", 18 | "test:e2e:run": "start-test \"yarn --cwd ../api dev\" 8055 dev 3333 \"npx cypress run\"" 19 | }, 20 | "dependencies": { 21 | "@cool-stack/api": "*", 22 | "@cool-stack/configs": "*", 23 | "@cool-stack/ui": "*", 24 | "@directus/sdk": "10.3.5", 25 | "@remix-run/architect": "1.19.3", 26 | "@remix-run/node": "1.19.3", 27 | "@remix-run/react": "1.19.3", 28 | "cross-env": "7.0.3", 29 | "react": "18.2.0", 30 | "react-dom": "18.2.0", 31 | "tiny-invariant": "1.3.3" 32 | }, 33 | "devDependencies": { 34 | "@architect/architect": "10.16.3", 35 | "@remix-run/dev": "1.19.3", 36 | "@remix-run/eslint-config": "1.19.3", 37 | "@testing-library/cypress": "9.0.0", 38 | "@testing-library/jest-dom": "5.17.0", 39 | "@testing-library/react": "13.4.0", 40 | "@types/jest": "29.5.12", 41 | "@types/react": "18.0.26", 42 | "@types/react-dom": "18.0.9", 43 | "@typescript-eslint/eslint-plugin": "5.62.0", 44 | "@typescript-eslint/parser": "5.62.0", 45 | "aws-sdk": "2.1691.0", 46 | "concurrently": "7.6.0", 47 | "cypress": "12.17.4", 48 | "eslint": "8.57.0", 49 | "eslint-config-prettier": "8.10.0", 50 | "eslint-plugin-import": "2.30.0", 51 | "eslint-plugin-jest-dom": "4.0.3", 52 | "eslint-plugin-prettier": "4.2.1", 53 | "eslint-plugin-react": "7.35.2", 54 | "eslint-plugin-react-hooks": "4.6.2", 55 | "eslint-plugin-unicorn": "45.0.2", 56 | "jest": "29.7.0", 57 | "jest-environment-jsdom": "29.7.0", 58 | "open-cli": "7.2.0", 59 | "prettier": "2.8.8", 60 | "prettier-plugin-tailwindcss": "0.6.6", 61 | "rimraf": "3.0.2", 62 | "start-server-and-test": "1.15.4", 63 | "tailwindcss": "3.4.10", 64 | "ts-jest": "29.2.5", 65 | "typescript": "4.9.5" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /page/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdsoftpl/cool-stack/bed7a407022d217ff818857c1263a4ba5f9d2b5c/page/public/favicon.ico -------------------------------------------------------------------------------- /page/public/images/cool-stack-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdsoftpl/cool-stack/bed7a407022d217ff818857c1263a4ba5f9d2b5c/page/public/images/cool-stack-cover.png -------------------------------------------------------------------------------- /page/remix.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | serverBuildTarget: "arc", 3 | server: "./server.js", 4 | ignoredRouteFiles: ["**/.*"], 5 | cacheDirectory: "./node_modules/.cache/remix", 6 | serverDependenciesToBundle: [ 7 | "@cool-stack/api", //used only for model types and it's type guards 8 | "@cool-stack/ui" 9 | ], 10 | appDirectory: "app", 11 | assetsBuildDirectory: "public/build", 12 | serverBuildPath: "server/index.js", 13 | publicPath: "/_static/build/", 14 | devServerPort: 3334 15 | }; 16 | -------------------------------------------------------------------------------- /page/remix.env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /page/server.js: -------------------------------------------------------------------------------- 1 | import {createRequestHandler} from "@remix-run/architect"; 2 | import * as build from "@remix-run/dev/server-build"; 3 | 4 | export const handler = createRequestHandler({ 5 | build, 6 | mode: process.env.NODE_ENV 7 | }); 8 | -------------------------------------------------------------------------------- /page/server/config.arc: -------------------------------------------------------------------------------- 1 | @aws 2 | runtime nodejs14.x 3 | # memory 1152 4 | # timeout 30 5 | # concurrency 1 6 | -------------------------------------------------------------------------------- /page/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require("@cool-stack/configs/tailwind/tailwind.config")], 3 | content: ["./app/**/*.{ts,tsx,jsx,js}", "../packages/ui/src/**/*.{ts,tsx,jsx,js}"] 4 | }; 5 | -------------------------------------------------------------------------------- /page/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cool-stack/configs/tsconfig/base.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "allowJs": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "lib": ["DOM", "DOM.Iterable", "ES2019"], 8 | "strict": true, 9 | "target": "ES2019", 10 | "esModuleInterop": true, 11 | "isolatedModules": true, 12 | "jsx": "react-jsx", 13 | "moduleResolution": "node", 14 | "noEmit": true, 15 | "resolveJsonModule": true, 16 | "paths": { 17 | "~/*": ["./app/*"] 18 | } 19 | }, 20 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "semanticCommits": true, 3 | "requiredStatusChecks": null, 4 | "additionalBranchPrefix": "{{parentDir}}-", 5 | "packageRules": [{ 6 | "updateTypes": ["minor", "patch", "pin", "digest"], 7 | "automerge": true 8 | }], 9 | "extends": [ 10 | "config:base" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "pipeline": { 4 | "dev": { 5 | "cache": false 6 | }, 7 | "clean": { 8 | "cache": false 9 | }, 10 | "build": { 11 | "dependsOn": [ 12 | "^build" 13 | ], 14 | "outputs": [ 15 | "build/**", 16 | "public/build/**", 17 | "src/style.css" 18 | ] 19 | }, 20 | "test": { 21 | "dependsOn": [ 22 | "^build" 23 | ], 24 | "outputs": [ 25 | "coverage/**" 26 | ] 27 | }, 28 | "lint": { 29 | "outputs": [] 30 | }, 31 | "generate:css": { 32 | "outputs": [] 33 | } 34 | } 35 | } 36 | --------------------------------------------------------------------------------