Remove Next.js/React legacy files
The project migrated to Angular 18; Next.js artifacts were still present. Deleted: - next.config.js, next-env.d.ts (.next/ was already gitignored) - public/ (favicons/fonts/images duplicated in src/assets/; not served by Angular) - .env.development, .env.production, .env.sample (Angular uses src/environments/) - src/middleware.ts (Next.js middleware) - src/modules/ (React TSX components — article, auth, profile) - src/utils/ (Next.js api client, iron-session auth, types) - src/config/ (process.env-based constants) - src/generated/ (openapi-typescript generated types) - src/jumps_02.json (only referenced as a commented-out import) - docs/ (empty directories) Updated .gitignore to block .env.* and next.config.js from being re-added.
@@ -1,2 +0,0 @@
|
||||
APP_ENV="development"
|
||||
API_BASE_URL=http://localhost:3201
|
||||
@@ -1,2 +0,0 @@
|
||||
APP_ENV="production"
|
||||
API_BASE_URL=http://api.adastra-cbd.com/api
|
||||
@@ -1,2 +0,0 @@
|
||||
APP_ENV="development"
|
||||
API_BASE_URL=http://localhost:3200/api
|
||||
@@ -54,5 +54,11 @@ sonar.properties
|
||||
**/*.copy.scss
|
||||
**/*.copy.html
|
||||
|
||||
# Next.js (legacy, removed)
|
||||
.next/
|
||||
next-env.d.ts
|
||||
next.config.js
|
||||
|
||||
# Environment files (Angular uses src/environments/*.ts)
|
||||
.env
|
||||
.env.*
|
||||
@@ -1,6 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square70x70logo src="/ms-icon-70x70.png"/>
|
||||
<square150x150logo src="/ms-icon-150x150.png"/>
|
||||
<square310x310logo src="/ms-icon-310x310.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<font id="icomoon" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="home" d="M1024 369.556l-512 397.426-512-397.428v162.038l512 397.426 512-397.428zM896 384v-384h-256v256h-256v-256h-256v384l384 288z" />
|
||||
<glyph unicode="" glyph-name="pencil" d="M864 960c88.364 0 160-71.634 160-160 0-36.020-11.91-69.258-32-96l-64-64-224 224 64 64c26.742 20.090 59.978 32 96 32zM64 224l-64-288 288 64 592 592-224 224-592-592zM715.578 596.422l-448-448-55.156 55.156 448 448 55.156-55.156z" />
|
||||
<glyph unicode="" glyph-name="cart" d="M384 32c0-53.019-42.981-96-96-96s-96 42.981-96 96c0 53.019 42.981 96 96 96s96-42.981 96-96zM1024 32c0-53.019-42.981-96-96-96s-96 42.981-96 96c0 53.019 42.981 96 96 96s96-42.981 96-96zM1024 448v384h-768c0 35.346-28.654 64-64 64h-192v-64h128l48.074-412.054c-29.294-23.458-48.074-59.5-48.074-99.946 0-70.696 57.308-128 128-128h768v64h-768c-35.346 0-64 28.654-64 64 0 0.218 0.014 0.436 0.016 0.656l831.984 127.344z" />
|
||||
<glyph unicode="" glyph-name="phone" d="M704 320c-64-64-64-128-128-128s-128 64-192 128-128 128-128 192 64 64 128 128-128 256-192 256-192-192-192-192c0-128 131.5-387.5 256-512s384-256 512-256c0 0 192 128 192 192s-192 256-256 192z" />
|
||||
<glyph unicode="" glyph-name="envelop" d="M928 832h-832c-52.8 0-96-43.2-96-96v-640c0-52.8 43.2-96 96-96h832c52.8 0 96 43.2 96 96v640c0 52.8-43.2 96-96 96zM398.74 409.628l-270.74-210.892v501.642l270.74-290.75zM176.38 704h671.24l-335.62-252-335.62 252zM409.288 398.302l102.712-110.302 102.71 110.302 210.554-270.302h-626.528l210.552 270.302zM625.26 409.628l270.74 290.75v-501.642l-270.74 210.892z" />
|
||||
<glyph unicode="" glyph-name="calendar" d="M320 576h128v-128h-128zM512 576h128v-128h-128zM704 576h128v-128h-128zM128 192h128v-128h-128zM320 192h128v-128h-128zM512 192h128v-128h-128zM320 384h128v-128h-128zM512 384h128v-128h-128zM704 384h128v-128h-128zM128 384h128v-128h-128zM832 960v-64h-128v64h-448v-64h-128v64h-128v-1024h960v1024h-128zM896 0h-832v704h832v-704z" />
|
||||
<glyph unicode="" glyph-name="user" d="M576 253.388v52.78c70.498 39.728 128 138.772 128 237.832 0 159.058 0 288-192 288s-192-128.942-192-288c0-99.060 57.502-198.104 128-237.832v-52.78c-217.102-17.748-384-124.42-384-253.388h896c0 128.968-166.898 235.64-384 253.388z" />
|
||||
<glyph unicode="" glyph-name="earth" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512-0.002c-62.958 0-122.872 13.012-177.23 36.452l233.148 262.29c5.206 5.858 8.082 13.422 8.082 21.26v96c0 17.674-14.326 32-32 32-112.99 0-232.204 117.462-233.374 118.626-6 6.002-14.14 9.374-22.626 9.374h-128c-17.672 0-32-14.328-32-32v-192c0-12.122 6.848-23.202 17.69-28.622l110.31-55.156v-187.886c-116.052 80.956-192 215.432-192 367.664 0 68.714 15.49 133.806 43.138 192h116.862c8.488 0 16.626 3.372 22.628 9.372l128 128c6 6.002 9.372 14.14 9.372 22.628v77.412c40.562 12.074 83.518 18.588 128 18.588 70.406 0 137.004-16.26 196.282-45.2-4.144-3.502-8.176-7.164-12.046-11.036-36.266-36.264-56.236-84.478-56.236-135.764s19.97-99.5 56.236-135.764c36.434-36.432 85.218-56.264 135.634-56.26 3.166 0 6.342 0.080 9.518 0.236 13.814-51.802 38.752-186.656-8.404-372.334-0.444-1.744-0.696-3.488-0.842-5.224-81.324-83.080-194.7-134.656-320.142-134.656z" />
|
||||
<glyph unicode="" glyph-name="link" d="M440.236 324.234c-13.31 0-26.616 5.076-36.77 15.23-95.134 95.136-95.134 249.934 0 345.070l192 192c46.088 46.086 107.36 71.466 172.534 71.466s126.448-25.38 172.536-71.464c95.132-95.136 95.132-249.934 0-345.070l-87.766-87.766c-20.308-20.308-53.23-20.308-73.54 0-20.306 20.306-20.306 53.232 0 73.54l87.766 87.766c54.584 54.586 54.584 143.404 0 197.99-26.442 26.442-61.6 41.004-98.996 41.004s-72.552-14.562-98.996-41.006l-192-191.998c-54.586-54.586-54.586-143.406 0-197.992 20.308-20.306 20.306-53.232 0-73.54-10.15-10.152-23.462-15.23-36.768-15.23zM256-52c-65.176 0-126.45 25.38-172.534 71.464-95.134 95.136-95.134 249.934 0 345.070l87.764 87.764c20.308 20.306 53.234 20.306 73.54 0 20.308-20.306 20.308-53.232 0-73.54l-87.764-87.764c-54.586-54.586-54.586-143.406 0-197.992 26.44-26.44 61.598-41.002 98.994-41.002s72.552 14.562 98.998 41.006l192 191.998c54.584 54.586 54.584 143.406 0 197.992-20.308 20.308-20.306 53.232 0 73.54 20.306 20.306 53.232 20.306 73.54-0.002 95.132-95.134 95.132-249.932 0.002-345.068l-192.002-192c-46.090-46.088-107.364-71.466-172.538-71.466z" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Logo_Ad_Astra" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" width="218px" height="48px" viewBox="0 0 218 48" enable-background="new 0 0 218 48" xml:space="preserve">
|
||||
<g id="Ad_Astra_3_">
|
||||
<g>
|
||||
<polygon fill="#D7C067" points="66.158,26.352 64.543,20.319 54.508,20.319 52.896,26.352 "/>
|
||||
<polygon fill="#D7C067" points="62.985,38.615 63.716,41.5 70.209,41.5 66.724,28.614 60.308,28.614 61.658,33.665 57.394,33.665
|
||||
58.745,28.614 52.25,28.614 48.842,41.5 55.299,41.5 56.069,38.615 "/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#D7C067" d="M92.467,26.352v-1.212c0-1.325-0.508-2.472-1.51-3.409c-1.003-0.937-2.23-1.412-3.647-1.412H71.733v6.033
|
||||
H92.467z"/>
|
||||
<path fill="#D7C067" d="M92.467,28.614h-6.456v6.853H78.19v-6.853h-6.457V41.5H87.31c1.418,0,2.645-0.475,3.647-1.411
|
||||
c1.002-0.938,1.51-2.083,1.51-3.409V28.614z"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon fill="#D7C067" points="119.495,26.352 117.88,20.319 107.846,20.319 106.232,26.352 "/>
|
||||
<polygon fill="#D7C067" points="116.322,38.615 117.053,41.5 123.546,41.5 120.061,28.614 113.644,28.614 114.995,33.665
|
||||
110.73,33.665 112.081,28.614 105.588,28.614 102.179,41.5 108.635,41.5 109.406,38.615 "/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#D7C067" d="M145.803,33.434c0-1.324-0.508-2.471-1.509-3.408c-1.002-0.937-2.229-1.412-3.647-1.412h-15.571
|
||||
c0.056,1.229,0.561,2.297,1.505,3.179c1.001,0.936,2.229,1.41,3.647,1.41h9.121v2.264H125.07V41.5h15.576
|
||||
c1.418,0,2.645-0.475,3.647-1.411c1.001-0.938,1.509-2.083,1.509-3.409V33.434z"/>
|
||||
<path fill="#D7C067" d="M125.07,26.352h20.732v-6.033h-15.575c-1.417,0-2.646,0.475-3.647,1.412
|
||||
c-1.002,0.936-1.51,2.084-1.51,3.409V26.352z"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="155.362" y="28.614" fill="#D7C067" width="6.456" height="12.886"/>
|
||||
<rect x="148.223" y="20.319" fill="#D7C067" width="20.733" height="6.033"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#D7C067" d="M192.109,26.352v-1.212c0-1.325-0.508-2.472-1.51-3.409c-1.003-0.937-2.23-1.412-3.647-1.412h-15.576
|
||||
v6.033H192.109z"/>
|
||||
<path fill="#D7C067" d="M171.375,41.5h6.456v-8.296h7.823V41.5h6.456v-8.066c0-0.679-0.481-1.473-1.432-2.362l-0.174-0.163
|
||||
l0.174-0.163c0.846-0.79,1.319-1.506,1.414-2.132h-20.716V41.5z"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon fill="#D7C067" points="207.776,38.615 208.508,41.5 215,41.5 211.515,28.614 205.099,28.614 206.45,33.665
|
||||
202.185,33.665 203.536,28.614 197.041,28.614 193.632,41.5 200.088,41.5 200.861,38.615 "/>
|
||||
<polygon fill="#D7C067" points="210.948,26.352 209.335,20.319 199.3,20.319 197.686,26.352 "/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Star_1_">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#DDDCDB" d="M67.011,15.14c-15.125-0.415-30.13,11.212-42.833,19.917
|
||||
l5.321-16.303l2.163,5.883c0.43,1.075,0.917,0.809,2.133,0.276C45.682,19.162,51.53,13.062,67.011,15.14z"/>
|
||||
<path fill="#D7C067" d="M21.111,39.24l8.584-6.51l9.078,9.012l-8.772-5.538L16.491,46l4.396-17.216L3,19.504l21.795,7.159
|
||||
L21.111,39.24z"/>
|
||||
<path fill="#D7C067" d="M25.738,23.656L7.23,18.529l16.482,1.101l2.655-8.707l2.032,5.081L25.738,23.656z"/>
|
||||
<path fill="#D7C067" d="M28.784,3l6.61,15.685c0.112,0.257,0.211,0.435,0.442,0.326c9.763-4.918,17.872-12.196,41.689-4.196
|
||||
c-25.071-5.188-30.394,2.068-43.894,8.159c-0.269,0.111-0.497-0.09-0.729-0.662L27.238,8.07L28.784,3z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Logo_Ad_Astra" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" width="272px" height="60px" viewBox="0 0 272 60" enable-background="new 0 0 272 60" xml:space="preserve">
|
||||
<g id="Ad_Astra_3_">
|
||||
<g>
|
||||
<polygon fill="#D7C067" points="82.246,32.325 80.22,24.75 67.628,24.75 65.604,32.325 "/>
|
||||
<polygon fill="#D7C067" points="78.265,47.726 79.182,51.349 87.329,51.349 82.955,35.167 74.905,35.167 76.599,41.509
|
||||
71.25,41.509 72.944,35.166 64.795,35.167 60.519,51.349 68.62,51.349 69.587,47.726 "/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#D7C067" d="M115.256,32.325v-1.523c0-1.663-0.637-3.104-1.895-4.28c-1.258-1.176-2.797-1.773-4.575-1.773H89.241
|
||||
v7.576H115.256z"/>
|
||||
<path fill="#D7C067" d="M115.256,35.167h-8.1v8.606h-9.814v-8.606h-8.101v16.182h19.544c1.779,0,3.318-0.596,4.575-1.772
|
||||
c1.258-1.177,1.895-2.617,1.895-4.28V35.167z"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon fill="#D7C067" points="149.168,32.325 147.142,24.75 134.552,24.75 132.527,32.325 "/>
|
||||
<polygon fill="#D7C067" points="145.187,47.726 146.104,51.349 154.25,51.349 149.878,35.167 141.827,35.167 143.522,41.509
|
||||
138.171,41.509 139.866,35.166 131.718,35.167 127.441,51.349 135.542,51.349 136.51,47.726 "/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#D7C067" d="M182.177,41.219c0-1.662-0.637-3.103-1.894-4.279c-1.257-1.177-2.797-1.773-4.576-1.773h-19.537
|
||||
c0.07,1.543,0.704,2.885,1.888,3.992c1.257,1.175,2.797,1.771,4.576,1.771h11.444v2.843h-17.915v7.576h19.543
|
||||
c1.779,0,3.319-0.596,4.576-1.772c1.256-1.177,1.894-2.617,1.894-4.28V41.219z"/>
|
||||
<path fill="#D7C067" d="M156.164,32.325h26.013V24.75h-19.542c-1.778,0-3.319,0.597-4.576,1.773
|
||||
c-1.257,1.175-1.895,2.617-1.895,4.28V32.325z"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="194.171" y="35.167" fill="#D7C067" width="8.101" height="16.182"/>
|
||||
<rect x="185.214" y="24.75" fill="#D7C067" width="26.015" height="7.576"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#D7C067" d="M240.278,32.325v-1.523c0-1.663-0.637-3.104-1.895-4.28c-1.259-1.176-2.798-1.773-4.576-1.773h-19.544
|
||||
v7.576H240.278z"/>
|
||||
<path fill="#D7C067" d="M214.264,51.349h8.1V40.93h9.815v10.418h8.1V41.219c0-0.852-0.604-1.85-1.796-2.966l-0.219-0.204
|
||||
l0.219-0.205c1.061-0.992,1.655-1.891,1.774-2.677h-25.992V51.349z"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon fill="#D7C067" points="259.936,47.726 260.854,51.349 269,51.349 264.627,35.167 256.577,35.167 258.272,41.509
|
||||
252.92,41.509 254.616,35.166 246.466,35.167 242.189,51.349 250.291,51.349 251.26,47.726 "/>
|
||||
<polygon fill="#D7C067" points="263.916,32.325 261.893,24.75 249.301,24.75 247.275,32.325 "/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Star_1_">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#DDDCDB" d="M83.315,18.245c-18.977-0.521-37.805,14.08-53.743,25.012
|
||||
l6.676-20.473l2.715,7.387c0.54,1.35,1.15,1.016,2.676,0.347C56.554,23.297,63.892,15.637,83.315,18.245z"/>
|
||||
<path fill="#D7C067" d="M25.725,48.51l10.771-8.175l11.39,11.317l-11.007-6.955L19.927,57l5.516-21.621L3,23.726l27.347,8.99
|
||||
L25.725,48.51z"/>
|
||||
<path fill="#D7C067" d="M31.53,28.94L8.308,22.501l20.681,1.383l3.331-10.934l2.55,6.38L31.53,28.94z"/>
|
||||
<path fill="#D7C067" d="M35.352,3l8.294,19.697c0.14,0.322,0.265,0.545,0.554,0.408C56.45,16.93,66.625,7.79,96.508,17.836
|
||||
c-31.457-6.516-38.135,2.598-55.074,10.246c-0.337,0.14-0.624-0.113-0.914-0.832L33.412,9.368L35.352,3z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/favicon\/android-icon-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "\/favicon\/android-icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "\/favicon\/android-icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "\/favicon\/android-icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "\/favicon\/android-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "\/favicon\/android-icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
@@ -1,3 +0,0 @@
|
||||
export const API_BASE_URL = process.env.API_BASE_URL;
|
||||
|
||||
export const SECURE_COOKIE = process.env.NODE_ENV === "production";
|
||||
@@ -1,840 +0,0 @@
|
||||
/**
|
||||
* This file was auto-generated by openapi-typescript.
|
||||
* Do not make direct changes to the file.
|
||||
*/
|
||||
|
||||
export interface paths {
|
||||
"/users/login": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Existing user login
|
||||
* @description Login for existing user
|
||||
*/
|
||||
post: operations["Login"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/users": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** @description Register a new user */
|
||||
post: operations["CreateUser"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/user": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Get current user
|
||||
* @description Gets the currently logged-in user
|
||||
*/
|
||||
get: operations["GetCurrentUser"];
|
||||
/**
|
||||
* Update current user
|
||||
* @description Updated user information for current user
|
||||
*/
|
||||
put: operations["UpdateCurrentUser"];
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/profiles/{username}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Get a profile
|
||||
* @description Get a profile of a user of the system. Auth is optional
|
||||
*/
|
||||
get: operations["GetProfileByUsername"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/profiles/{username}/follow": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Follow a user
|
||||
* @description Follow a user by username
|
||||
*/
|
||||
post: operations["FollowUserByUsername"];
|
||||
/**
|
||||
* Unfollow a user
|
||||
* @description Unfollow a user by username
|
||||
*/
|
||||
delete: operations["UnfollowUserByUsername"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/articles/feed": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Get recent articles from users you follow
|
||||
* @description Get most recent articles from users you follow. Use query parameters to limit. Auth is required
|
||||
*/
|
||||
get: operations["GetArticlesFeed"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/articles": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Get recent articles globally
|
||||
* @description Get most recent articles globally. Use query parameters to filter results. Auth is optional
|
||||
*/
|
||||
get: operations["GetArticles"];
|
||||
put?: never;
|
||||
/**
|
||||
* Create an article
|
||||
* @description Create an article. Auth is required
|
||||
*/
|
||||
post: operations["CreateArticle"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/articles/{slug}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Get an article
|
||||
* @description Get an article. Auth not required
|
||||
*/
|
||||
get: operations["GetArticle"];
|
||||
/**
|
||||
* Update an article
|
||||
* @description Update an article. Auth is required
|
||||
*/
|
||||
put: operations["UpdateArticle"];
|
||||
post?: never;
|
||||
/**
|
||||
* Delete an article
|
||||
* @description Delete an article. Auth is required
|
||||
*/
|
||||
delete: operations["DeleteArticle"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/articles/{slug}/comments": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Get comments for an article
|
||||
* @description Get the comments for an article. Auth is optional
|
||||
*/
|
||||
get: operations["GetArticleComments"];
|
||||
put?: never;
|
||||
/**
|
||||
* Create a comment for an article
|
||||
* @description Create a comment for an article. Auth is required
|
||||
*/
|
||||
post: operations["CreateArticleComment"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/articles/{slug}/comments/{id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
post?: never;
|
||||
/**
|
||||
* Delete a comment for an article
|
||||
* @description Delete a comment for an article. Auth is required
|
||||
*/
|
||||
delete: operations["DeleteArticleComment"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/articles/{slug}/favorite": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Favorite an article
|
||||
* @description Favorite an article. Auth is required
|
||||
*/
|
||||
post: operations["CreateArticleFavorite"];
|
||||
/**
|
||||
* Unfavorite an article
|
||||
* @description Unfavorite an article. Auth is required
|
||||
*/
|
||||
delete: operations["DeleteArticleFavorite"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/tags": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Get tags
|
||||
* @description Get tags. Auth not required
|
||||
*/
|
||||
get: operations["GetTags"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
}
|
||||
export type webhooks = Record<string, never>;
|
||||
export interface components {
|
||||
schemas: {
|
||||
LoginUser: {
|
||||
email: string;
|
||||
/** Format: password */
|
||||
password: string;
|
||||
};
|
||||
NewUser: {
|
||||
username: string;
|
||||
email: string;
|
||||
/** Format: password */
|
||||
password: string;
|
||||
};
|
||||
User: {
|
||||
email: string;
|
||||
token: string;
|
||||
username: string;
|
||||
bio: string;
|
||||
image: string;
|
||||
};
|
||||
UpdateUser: {
|
||||
email?: string;
|
||||
password?: string;
|
||||
username?: string;
|
||||
bio?: string;
|
||||
image?: string;
|
||||
};
|
||||
Profile: {
|
||||
username: string;
|
||||
bio: string;
|
||||
image: string;
|
||||
following: boolean;
|
||||
};
|
||||
Article: {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
body: string;
|
||||
tagList: string[];
|
||||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
/** Format: date-time */
|
||||
updatedAt: string;
|
||||
favorited: boolean;
|
||||
favoritesCount: number;
|
||||
author: components["schemas"]["Profile"];
|
||||
};
|
||||
NewArticle: {
|
||||
title: string;
|
||||
description: string;
|
||||
body: string;
|
||||
tagList?: string[];
|
||||
};
|
||||
UpdateArticle: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
body?: string;
|
||||
};
|
||||
Comment: {
|
||||
id: string;
|
||||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
/** Format: date-time */
|
||||
updatedAt: string;
|
||||
body: string;
|
||||
author: components["schemas"]["Profile"];
|
||||
};
|
||||
NewComment: {
|
||||
body: string;
|
||||
};
|
||||
GenericErrorModel: {
|
||||
errors: {
|
||||
body: string[];
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Tags */
|
||||
TagsResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
tags: string[];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Single comment */
|
||||
SingleCommentResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
comment: components["schemas"]["Comment"];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Multiple comments */
|
||||
MultipleCommentsResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
comments: components["schemas"]["Comment"][];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Single article */
|
||||
SingleArticleResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
article: components["schemas"]["Article"];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Multiple articles */
|
||||
MultipleArticlesResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
articles: {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
tagList: string[];
|
||||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
/** Format: date-time */
|
||||
updatedAt: string;
|
||||
favorited: boolean;
|
||||
favoritesCount: number;
|
||||
author: components["schemas"]["Profile"];
|
||||
}[];
|
||||
articlesCount: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Profile */
|
||||
ProfileResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
profile: components["schemas"]["Profile"];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description User */
|
||||
UserResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
user: components["schemas"]["User"];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description No content */
|
||||
EmptyOkResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Unauthorized */
|
||||
Unauthorized: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Unexpected error */
|
||||
GenericError: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["GenericErrorModel"];
|
||||
};
|
||||
};
|
||||
};
|
||||
parameters: {
|
||||
/** @description The number of items to skip before starting to collect the result set. */
|
||||
offsetParam: number;
|
||||
/** @description The numbers of items to return. */
|
||||
limitParam: number;
|
||||
};
|
||||
requestBodies: {
|
||||
/** @description Credentials to use */
|
||||
LoginUserRequest: {
|
||||
content: {
|
||||
"application/json": {
|
||||
user: components["schemas"]["LoginUser"];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Details of the new user to register */
|
||||
NewUserRequest: {
|
||||
content: {
|
||||
"application/json": {
|
||||
user: components["schemas"]["NewUser"];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description User details to update. At least **one** field is required. */
|
||||
UpdateUserRequest: {
|
||||
content: {
|
||||
"application/json": {
|
||||
user: components["schemas"]["UpdateUser"];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Article to create */
|
||||
NewArticleRequest: {
|
||||
content: {
|
||||
"application/json": {
|
||||
article: components["schemas"]["NewArticle"];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Article to update */
|
||||
UpdateArticleRequest: {
|
||||
content: {
|
||||
"application/json": {
|
||||
article: components["schemas"]["UpdateArticle"];
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Comment you want to create */
|
||||
NewCommentRequest: {
|
||||
content: {
|
||||
"application/json": {
|
||||
comment: components["schemas"]["NewComment"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
headers: never;
|
||||
pathItems: never;
|
||||
}
|
||||
export type $defs = Record<string, never>;
|
||||
export interface operations {
|
||||
Login: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: components["requestBodies"]["LoginUserRequest"];
|
||||
responses: {
|
||||
200: components["responses"]["UserResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
CreateUser: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: components["requestBodies"]["NewUserRequest"];
|
||||
responses: {
|
||||
201: components["responses"]["UserResponse"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
GetCurrentUser: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["UserResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
UpdateCurrentUser: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: components["requestBodies"]["UpdateUserRequest"];
|
||||
responses: {
|
||||
200: components["responses"]["UserResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
GetProfileByUsername: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Username of the profile to get */
|
||||
username: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["ProfileResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
FollowUserByUsername: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Username of the profile you want to follow */
|
||||
username: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["ProfileResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
UnfollowUserByUsername: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Username of the profile you want to unfollow */
|
||||
username: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["ProfileResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
GetArticlesFeed: {
|
||||
parameters: {
|
||||
query?: {
|
||||
/** @description The number of items to skip before starting to collect the result set. */
|
||||
offset?: components["parameters"]["offsetParam"];
|
||||
/** @description The numbers of items to return. */
|
||||
limit?: components["parameters"]["limitParam"];
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["MultipleArticlesResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
GetArticles: {
|
||||
parameters: {
|
||||
query?: {
|
||||
/** @description Filter by tag */
|
||||
tag?: string;
|
||||
/** @description Filter by author (username) */
|
||||
author?: string;
|
||||
/** @description Filter by favorites of a user (username) */
|
||||
favorited?: string;
|
||||
/** @description The number of items to skip before starting to collect the result set. */
|
||||
offset?: components["parameters"]["offsetParam"];
|
||||
/** @description The numbers of items to return. */
|
||||
limit?: components["parameters"]["limitParam"];
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["MultipleArticlesResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
CreateArticle: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: components["requestBodies"]["NewArticleRequest"];
|
||||
responses: {
|
||||
201: components["responses"]["SingleArticleResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
GetArticle: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Slug of the article to get */
|
||||
slug: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["SingleArticleResponse"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
UpdateArticle: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Slug of the article to update */
|
||||
slug: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: components["requestBodies"]["UpdateArticleRequest"];
|
||||
responses: {
|
||||
200: components["responses"]["SingleArticleResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
DeleteArticle: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Slug of the article to delete */
|
||||
slug: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["EmptyOkResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
GetArticleComments: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Slug of the article that you want to get comments for */
|
||||
slug: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["MultipleCommentsResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
CreateArticleComment: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Slug of the article that you want to create a comment for */
|
||||
slug: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: components["requestBodies"]["NewCommentRequest"];
|
||||
responses: {
|
||||
200: components["responses"]["SingleCommentResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
DeleteArticleComment: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Slug of the article that you want to delete a comment for */
|
||||
slug: string;
|
||||
/** @description ID of the comment you want to delete */
|
||||
id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["EmptyOkResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
CreateArticleFavorite: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Slug of the article that you want to favorite */
|
||||
slug: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["SingleArticleResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
DeleteArticleFavorite: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Slug of the article that you want to unfavorite */
|
||||
slug: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["SingleArticleResponse"];
|
||||
401: components["responses"]["Unauthorized"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
GetTags: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["TagsResponse"];
|
||||
422: components["responses"]["GenericError"];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,926 +0,0 @@
|
||||
[
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-18T00:00:00.000+00:00",
|
||||
"numero": 608,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Chutas",
|
||||
"participants": 5,
|
||||
"sautants": ["Charles", "Guillaume", "Denis", "?"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Chutas/",
|
||||
"vidéo": "2023-08-18_S00608.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-18T00:00:00.000+00:00",
|
||||
"numero": 609,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Chutas",
|
||||
"participants": 4,
|
||||
"sautants": ["Cédric", "Guillaume", "Denis"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Chutas/",
|
||||
"vidéo": "2023-08-18_S00609.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-18T00:00:00.000+00:00",
|
||||
"numero": 610,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Chutas",
|
||||
"participants": 4,
|
||||
"sautants": ["Cédric", "Guillaume", "Denis"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Chutas/",
|
||||
"vidéo": "2023-08-18_S00610.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-19T00:00:00.000+00:00",
|
||||
"numero": 611,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-19T00:00:00.000+00:00",
|
||||
"numero": 612,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-19T00:00:00.000+00:00",
|
||||
"numero": 613,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-19T00:00:00.000+00:00",
|
||||
"numero": 614,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-19T00:00:00.000+00:00",
|
||||
"numero": 615,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "VR",
|
||||
"module": "",
|
||||
"participants": 6,
|
||||
"sautants": ["Lucie", "Julie", "Cédric", "Denis", "Guillaume", "$6"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-26T00:00:00.000+00:00",
|
||||
"numero": 616,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": ["Pascal", "Guillaume"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-26T00:00:00.000+00:00",
|
||||
"numero": 617,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-26T00:00:00.000+00:00",
|
||||
"numero": 618,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-26T00:00:00.000+00:00",
|
||||
"numero": 619,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-26T00:00:00.000+00:00",
|
||||
"numero": 620,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-27T00:00:00.000+00:00",
|
||||
"numero": 621,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Chutas",
|
||||
"participants": null,
|
||||
"sautants": ["Loïc"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Chutas/",
|
||||
"vidéo": "2023-08-27_S00621.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-08-27T00:00:00.000+00:00",
|
||||
"numero": 622,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Chutas",
|
||||
"participants": null,
|
||||
"sautants": ["Nitro", "Marine", "Mathilde"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Chutas/",
|
||||
"vidéo": "2023-08-27_S00622.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-03T00:00:00.000+00:00",
|
||||
"numero": 623,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Chutas",
|
||||
"participants": 3,
|
||||
"sautants": ["Pascal", "Guillaume"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": "2023-09-03_S00623.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-03T00:00:00.000+00:00",
|
||||
"numero": 624,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Chutas",
|
||||
"participants": 3,
|
||||
"sautants": ["Pascal", "Guillaume"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Chutas/",
|
||||
"vidéo": "2023-09-03_S00624.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-03T00:00:00.000+00:00",
|
||||
"numero": 625,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Track",
|
||||
"participants": 4,
|
||||
"sautants": ["Pascal", "Guillaume", "Lucie"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Track/",
|
||||
"vidéo": "2023-09-03_S00625.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-03T00:00:00.000+00:00",
|
||||
"numero": 626,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Track",
|
||||
"participants": 4,
|
||||
"sautants": ["Pascal", "Guillaume", "Lucie"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Track/",
|
||||
"vidéo": "2023-09-03_S00626.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-03T00:00:00.000+00:00",
|
||||
"numero": 627,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Track",
|
||||
"participants": 5,
|
||||
"sautants": ["Brebz", "Pascal", "Guillaume", "Lucie"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Track/",
|
||||
"vidéo": "2023-09-03_S00627.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-07T00:00:00.000+00:00",
|
||||
"numero": 628,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-07T00:00:00.000+00:00",
|
||||
"numero": 629,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-07T00:00:00.000+00:00",
|
||||
"numero": 630,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-07T00:00:00.000+00:00",
|
||||
"numero": 631,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-07T00:00:00.000+00:00",
|
||||
"numero": 632,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-08T00:00:00.000+00:00",
|
||||
"numero": 633,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-08T00:00:00.000+00:00",
|
||||
"numero": 634,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-08T00:00:00.000+00:00",
|
||||
"numero": 635,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 2000,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Chutas",
|
||||
"participants": 2,
|
||||
"sautants": ["Corentin"],
|
||||
"programme": "Saut Hélicoptère",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Chutas/",
|
||||
"vidéo": "2023-09-08_S00635.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-08T00:00:00.000+00:00",
|
||||
"numero": 636,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-08T00:00:00.000+00:00",
|
||||
"numero": 637,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-08T00:00:00.000+00:00",
|
||||
"numero": 638,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-08T00:00:00.000+00:00",
|
||||
"numero": 639,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-09T00:00:00.000+00:00",
|
||||
"numero": 640,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-09T00:00:00.000+00:00",
|
||||
"numero": 641,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-09T00:00:00.000+00:00",
|
||||
"numero": 642,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-09T00:00:00.000+00:00",
|
||||
"numero": 643,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "",
|
||||
"participants": null,
|
||||
"sautants": [],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "",
|
||||
"vidéo": ""
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-09T00:00:00.000+00:00",
|
||||
"numero": 644,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Track",
|
||||
"participants": null,
|
||||
"sautants": ["Joliane", "Edouard", "Guillaume", "Corentin", "Julie", "Warren"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "Plage",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Track/",
|
||||
"vidéo": "2023-09-09_S00644.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-24T00:00:00.000+00:00",
|
||||
"numero": 645,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Chutas",
|
||||
"participants": 3,
|
||||
"sautants": ["Edouard", "Jocelyn"],
|
||||
"programme": "Sortie trace puis verticalisation tête en haut.",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Chutas/",
|
||||
"vidéo": "2023-09-29_S00645.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-24T00:00:00.000+00:00",
|
||||
"numero": 646,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Chutas",
|
||||
"participants": 4,
|
||||
"sautants": ["Edouard", "Romain", "Jocelyn"],
|
||||
"programme": "Sortie trace puis verticalisation tête en haut.",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Chutas/",
|
||||
"vidéo": "2023-09-29_S00646.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-24T00:00:00.000+00:00",
|
||||
"numero": 647,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Trace",
|
||||
"participants": 4,
|
||||
"sautants": ["Edouard", "Romain", "Jocelyn"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Trace/",
|
||||
"vidéo": "2023-09-29_S00647.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-24T00:00:00.000+00:00",
|
||||
"numero": 648,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Trace",
|
||||
"participants": 4,
|
||||
"sautants": ["Edouard", "Romain", "Jocelyn"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Trace/",
|
||||
"vidéo": "2023-09-29_S00648.mp4"
|
||||
},
|
||||
{
|
||||
"author": {"$oid": "642c4459666702637dcb5066"},
|
||||
"date": "2023-09-24T00:00:00.000+00:00",
|
||||
"numero": 649,
|
||||
"lieu": "Arcachon",
|
||||
"oaci": "LFCH",
|
||||
"aeronef": "Pilatus",
|
||||
"imat": "F-GOMB",
|
||||
"hauteur": 4200,
|
||||
"deploiement": 1000,
|
||||
"voile": "Sabre 2",
|
||||
"taille": 135,
|
||||
"categorie": "FF",
|
||||
"module": "Trace",
|
||||
"participants": 5,
|
||||
"sautants": ["Cédric", "Edouard", "Romain", "Jocelyn"],
|
||||
"programme": "",
|
||||
"accessoires": "",
|
||||
"zone": "",
|
||||
"dossier": "/Volumes/Storage/Skydive/Videos/Trace/",
|
||||
"vidéo": "2023-09-29_S00649.mp4"
|
||||
}
|
||||
]
|
||||
@@ -1,34 +0,0 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getSession } from './utils/auth/session';
|
||||
|
||||
export const config = {
|
||||
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
|
||||
};
|
||||
|
||||
const publicRoutes = [
|
||||
/^\/article\/[^/]+?\/?$/, // /article/sample-slug
|
||||
/^\/login\/?$/,
|
||||
/^\/profile\/[^/]+?\/?$/, // /profile/sample-username
|
||||
/^\/profile\/[^/]+?\/favorites\/?$/, // /profile/sample-username/favorites
|
||||
/^\/register\/?$/,
|
||||
/^\/$/,
|
||||
] as const;
|
||||
|
||||
const isPublicRoutes = (pathname: string) => {
|
||||
return publicRoutes.some((route) => route.test(pathname));
|
||||
};
|
||||
|
||||
const isPrivateRoutes = (pathname: string) => {
|
||||
return !isPublicRoutes(pathname);
|
||||
};
|
||||
|
||||
const middleware = async (req: NextRequest) => {
|
||||
const session = await getSession();
|
||||
if (isPrivateRoutes(req.nextUrl.pathname) && session == null) {
|
||||
return NextResponse.redirect(new URL('/login', req.nextUrl));
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
};
|
||||
|
||||
export default middleware;
|
||||
@@ -1,137 +0,0 @@
|
||||
import { ExhaustiveError } from "@/utils/errors";
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
///////////////////
|
||||
// size
|
||||
|
||||
type Size = "sm" | "lg";
|
||||
|
||||
const DEFAULT_SIZE: Size = "sm";
|
||||
|
||||
const sizeToClassNameMaps: Record<Size, string> = {
|
||||
sm: "btn-sm",
|
||||
lg: "btn-lg",
|
||||
};
|
||||
|
||||
///////////////////
|
||||
// color
|
||||
|
||||
export type Color = "primary" | "secondary" | "info" | "success" | "warning" | "danger";
|
||||
|
||||
const DEFAULT_COLOR: Color = "primary";
|
||||
|
||||
///////////////////
|
||||
// variant
|
||||
|
||||
type Variant = "filled" | "outline";
|
||||
|
||||
const DEFAULT_VARIANT: Variant = "outline";
|
||||
|
||||
//////////////////////
|
||||
|
||||
const generateClassNameFromVariantAndColor = (variant: Variant, color: Color) => {
|
||||
const variantMaps: Record<Variant, string> = {
|
||||
filled: "btn-",
|
||||
outline: "btn-outline-",
|
||||
};
|
||||
|
||||
const colorMaps: Record<Color, string> = {
|
||||
primary: "primary",
|
||||
secondary: "secondary",
|
||||
info: "info",
|
||||
success: "success",
|
||||
warning: "warning",
|
||||
danger: "danger",
|
||||
};
|
||||
|
||||
return variantMaps[variant] + colorMaps[color];
|
||||
};
|
||||
|
||||
type CommonProps = {
|
||||
size?: Size;
|
||||
color?: Color;
|
||||
variant?: Variant;
|
||||
};
|
||||
|
||||
///////////////////
|
||||
// button pattern
|
||||
|
||||
type ButtonProps = ComponentPropsWithoutRef<"button"> &
|
||||
CommonProps & {
|
||||
component: "button";
|
||||
};
|
||||
|
||||
const ButtonAsButtonTag = ({
|
||||
className,
|
||||
children,
|
||||
size = DEFAULT_SIZE,
|
||||
color = DEFAULT_COLOR,
|
||||
variant = DEFAULT_VARIANT,
|
||||
...rest
|
||||
}: ButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
"btn",
|
||||
sizeToClassNameMaps[size],
|
||||
generateClassNameFromVariantAndColor(variant, color),
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
///////////////////
|
||||
// anchor pattern
|
||||
|
||||
type AnchorProps = ComponentPropsWithoutRef<"a"> &
|
||||
CommonProps & {
|
||||
href: string;
|
||||
component: "a";
|
||||
};
|
||||
|
||||
const ButtonAsAnchorTag = ({
|
||||
className,
|
||||
children,
|
||||
size = DEFAULT_SIZE,
|
||||
color = DEFAULT_COLOR,
|
||||
variant = DEFAULT_VARIANT,
|
||||
...rest
|
||||
}: AnchorProps) => {
|
||||
return (
|
||||
<Link
|
||||
className={clsx(
|
||||
"btn",
|
||||
sizeToClassNameMaps[size],
|
||||
generateClassNameFromVariantAndColor(variant, color),
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
|
||||
type Props = ButtonProps | AnchorProps;
|
||||
|
||||
export const Button = (props: Props) => {
|
||||
const componentType = props.component;
|
||||
|
||||
switch (componentType) {
|
||||
case "button":
|
||||
return <ButtonAsButtonTag {...props} />;
|
||||
case "a":
|
||||
return <ButtonAsAnchorTag {...props} />;
|
||||
default:
|
||||
// compilation fails if all cases are not covered
|
||||
throw new ExhaustiveError(componentType);
|
||||
}
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Button from "react-bootstrap/Button";
|
||||
import Dropdown from "react-bootstrap/Dropdown";
|
||||
import DropdownButton from "react-bootstrap/DropdownButton";
|
||||
import Offcanvas from "react-bootstrap/Offcanvas";
|
||||
|
||||
export interface ExampleOffcanvasProps {
|
||||
className?: string | undefined;
|
||||
}
|
||||
|
||||
const ExampleOffcanvas: React.FC<ExampleOffcanvasProps> = ({ className }) => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button size="sm" onClick={() => setShow((s) => !s)} className={className}>
|
||||
<span className="icon-cart me-2"></span>Mon panier
|
||||
</Button>
|
||||
<Offcanvas placement="end" show={show} onHide={() => setShow(false)}>
|
||||
<Offcanvas.Header closeButton>
|
||||
<Offcanvas.Title as="h5">Mon panier</Offcanvas.Title>
|
||||
</Offcanvas.Header>
|
||||
<Offcanvas.Body>
|
||||
<div>
|
||||
Some text as placeholder. In real life you can have the elements you
|
||||
have chosen. Like, text, images, lists, etc.
|
||||
</div>
|
||||
<DropdownButton variant="secondary" title="Dropdown button" className="mt-3">
|
||||
<Dropdown.Item href="#">Action</Dropdown.Item>
|
||||
<Dropdown.Item href="#">Another action</Dropdown.Item>
|
||||
<Dropdown.Item href="#">Something else here</Dropdown.Item>
|
||||
</DropdownButton>
|
||||
</Offcanvas.Body>
|
||||
</Offcanvas>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExampleOffcanvas;
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from "./button";
|
||||
export * from "./buttonOffcanvas";
|
||||
export * from "./linkPopover";
|
||||
@@ -1,22 +0,0 @@
|
||||
import Anchor from "react-bootstrap/Anchor";
|
||||
import OverlayTrigger from "react-bootstrap/OverlayTrigger";
|
||||
import Popover from "react-bootstrap/Popover";
|
||||
|
||||
const ExamplePopover: React.FC = () => {
|
||||
return (
|
||||
<OverlayTrigger
|
||||
trigger="click"
|
||||
placement="right"
|
||||
overlay={
|
||||
<Popover>
|
||||
<Popover.Header as="h3">Custom popover</Popover.Header>
|
||||
<Popover.Body>This is a Bootstrap popover.</Popover.Body>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<Anchor className="text-info">Example popover</Anchor>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExamplePopover;
|
||||
@@ -1,16 +0,0 @@
|
||||
type Props = {
|
||||
messages?: string[];
|
||||
};
|
||||
|
||||
export const ErrorMessage = ({ messages }: Props) => {
|
||||
return (
|
||||
messages &&
|
||||
messages.length > 0 && (
|
||||
<ul className="error-messages" aria-live="polite">
|
||||
{messages.map((message, index) => (
|
||||
<li key={index}>{message}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./errorMessage";
|
||||
@@ -1,107 +0,0 @@
|
||||
import { Container, Col, Row } from "react-bootstrap";
|
||||
import Link from "next/link";
|
||||
import LogoSmall from "@/app/_components/layoutElements/logoSmall";
|
||||
|
||||
export const Footer = async () => {
|
||||
return (
|
||||
<footer className="bd-footer pt-3 pb-2 pt-md-4 mt-5 bg-dark-blue gold">
|
||||
<Container className="py-3 py-md-4 px-4 px-md-3">
|
||||
<Row>
|
||||
<Col xs={12} lg={3}>
|
||||
<Link
|
||||
className="d-inline-flex align-items-center mb-2 text-body-emphasis text-decoration-none"
|
||||
href="/"
|
||||
aria-label="Bootstrap"
|
||||
>
|
||||
<LogoSmall />
|
||||
</Link>
|
||||
<p className="mt-2 mb-3 small">
|
||||
L'équipe Ad Astra est réactive et à l'écoute de vos demandes afin de vous accompagner au mieux
|
||||
et satisfaire vos besoins en fleurs et hashes CBD de qualité.
|
||||
</p>
|
||||
</Col>
|
||||
<Col xs={6} lg={{ span: 2, offset: 1 }} className="mb-3 mt-3">
|
||||
<h5>Nos produits</h5>
|
||||
<ul className="list-unstyled">
|
||||
<li className="mb-2">
|
||||
<i className="icon-home me-2" />
|
||||
<Link href="/">Accueil</Link>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="icon-marijuana me-2" />
|
||||
<Link href="/produits/les-fleurs">Fleurs</Link>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="icon-hash me-2" />
|
||||
<Link href="/produits/les-resines">Résines</Link>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="icon-molecule me-2" />
|
||||
<Link href="/produits/les-molecules">10OH+</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</Col>
|
||||
<Col xs={6} lg={2} className="mb-3 mt-3">
|
||||
<h5>Documentation</h5>
|
||||
<ul className="list-unstyled">
|
||||
<li className="mb-2">
|
||||
<Link href="/article/les-fleurs">Les fleurs</Link>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<Link href="/article/les-resines">Les hashes</Link>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<Link href="/article/les-molecules">Les molécules</Link>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<Link href="/article/les-concentres">Les concentrés</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</Col>
|
||||
<Col xs={6} lg={2} className="mb-3 mt-3">
|
||||
<h5>Professionels</h5>
|
||||
<ul className="list-unstyled">
|
||||
<li className="mb-2">
|
||||
<Link href="/catalogue_ad_astra_2025.pdf" target="_blank" rel="noopener">
|
||||
Catalogue B2B
|
||||
</Link>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<Link href="/pro/lien2" target="_blank" rel="noopener">
|
||||
Lien 2
|
||||
</Link>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<Link href="/pro/lien3" target="_blank" rel="noopener">
|
||||
Lien 3
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</Col>
|
||||
<Col xs={6} lg={2} className="mb-3 mt-3">
|
||||
<h5>Contact</h5>
|
||||
<ul className="list-unstyled">
|
||||
<li className="mb-2">
|
||||
<Link href="/contact" target="_blank" rel="noopener">
|
||||
Lien 1
|
||||
</Link>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<Link href="/mentions-legales" target="_blank" rel="noopener">
|
||||
Lien 2
|
||||
</Link>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<Link href="/produits" target="_blank" rel="noopener">
|
||||
Lien 3
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
<hr className="my-3" />
|
||||
<p className="px-4 mb-2 small">Copyright © 2025 Ad Astra</p>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./footer";
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./pagination";
|
||||
@@ -1,24 +0,0 @@
|
||||
import clsx from "clsx";
|
||||
import { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
type PaginationItemProps = ComponentPropsWithoutRef<"li"> & {
|
||||
href?: string;
|
||||
active?: boolean;
|
||||
};
|
||||
|
||||
export const PaginationItem = ({ href, active, className, children, ...rest }: PaginationItemProps) => (
|
||||
<li className={clsx("page-item", className, active && "active")} {...rest}>
|
||||
<a className="page-link" href={href}>
|
||||
{children}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
|
||||
type PaginationProps = ComponentPropsWithoutRef<"ul">;
|
||||
|
||||
export const Pagination = ({ className, children, ...rest }: PaginationProps) =>
|
||||
children && (
|
||||
<ul className={clsx("pagination", className)} {...rest}>
|
||||
{children}
|
||||
</ul>
|
||||
);
|
||||
@@ -1,13 +0,0 @@
|
||||
export const calcTotalPageNumber = (totalItemsCount: number, itemsPerPage: number) => {
|
||||
const quotient = Math.floor(totalItemsCount / itemsPerPage);
|
||||
const remainder = totalItemsCount % itemsPerPage;
|
||||
|
||||
return remainder === 0 ? quotient : quotient + 1;
|
||||
};
|
||||
|
||||
export const calcOffsetLimitFromPageNumber = (pageNumber: number, articlesPerPage: number) => {
|
||||
return {
|
||||
offset: (pageNumber - 1) * articlesPerPage,
|
||||
limit: articlesPerPage,
|
||||
};
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
export const decodePathParams = <TObj extends Record<string, string>, TKey extends keyof TObj>(
|
||||
pathParams: TObj,
|
||||
): Record<TKey, string> => {
|
||||
const decodedKeyValues = Object.entries(pathParams).map(([key, value]) => [key, decodeURIComponent(value)]);
|
||||
return Object.fromEntries(decodedKeyValues);
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ArticlePreview } from "@/utils/types/models";
|
||||
import { Tag } from "../tag";
|
||||
import { FavoriteButton } from "../favoriteButton";
|
||||
|
||||
type Props = {
|
||||
article: ArticlePreview;
|
||||
};
|
||||
|
||||
export const ArticleCard = ({ article }: Props) => {
|
||||
const author = article.author;
|
||||
|
||||
return (
|
||||
<div className="article-preview">
|
||||
<div className="article-meta">
|
||||
<Link href={`/profile/${author.username}`}>{author.image && <Image src={author.image} alt="" />}</Link>
|
||||
<div className="info">
|
||||
<Link href={`/profile/${author.username}`} className="author">
|
||||
{author.username}
|
||||
</Link>
|
||||
<span className="date">{article.createdAt.toDateString()}</span>
|
||||
</div>
|
||||
<FavoriteButton {...article} showMessage={false} className="pull-xs-right" />
|
||||
</div>
|
||||
<Link href={`/article/${article.slug}`} className="preview-link">
|
||||
<h1>{article.title}</h1>
|
||||
<p>{article.description}</p>
|
||||
<span>Read more...</span>
|
||||
<ul className="tag-list">
|
||||
{article.tagList.map((tag, index) => (
|
||||
<Tag component="li" key={index} variant="outline">
|
||||
{tag}
|
||||
</Tag>
|
||||
))}
|
||||
</ul>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./articleCard";
|
||||
@@ -1,91 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { createApiClient } from "@/utils/api/apiClient";
|
||||
import { getSession } from "@/utils/auth/session";
|
||||
import { parseWithZod } from "@conform-to/zod";
|
||||
import { redirect } from "next/navigation";
|
||||
import { inputsSchema } from "./types";
|
||||
|
||||
export const createArticleAction = async (_prevState: unknown, formData: FormData) => {
|
||||
if ((await getSession()) == null) {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
const submission = parseWithZod(formData, { schema: inputsSchema });
|
||||
|
||||
if (submission.status !== "success") {
|
||||
return submission.reply();
|
||||
}
|
||||
|
||||
const { slug: _slug, tag: _tag, ...article } = submission.value;
|
||||
|
||||
const client = createApiClient({
|
||||
path: "/articles",
|
||||
method: "post",
|
||||
params: {
|
||||
body: {
|
||||
article,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.sendRequest();
|
||||
|
||||
if (response.result === "success") {
|
||||
redirect(`/article/${response.data.article.slug}`);
|
||||
}
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 422:
|
||||
return submission.reply({
|
||||
formErrors: Object.values(response.error.errors).flat(),
|
||||
});
|
||||
default:
|
||||
throw new Error("api error");
|
||||
}
|
||||
};
|
||||
|
||||
export const updateArticleAction = async (_prevState: unknown, formData: FormData) => {
|
||||
if ((await getSession()) == null) {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
const submission = parseWithZod(formData, { schema: inputsSchema });
|
||||
|
||||
if (submission.status !== "success") {
|
||||
return submission.reply();
|
||||
}
|
||||
|
||||
const { slug, tag: _tag, ...article } = submission.value;
|
||||
if (slug == null) {
|
||||
throw new Error("Slug is unexpectedly set to null or undefined.");
|
||||
}
|
||||
|
||||
const client = createApiClient({
|
||||
path: "/articles/{slug}",
|
||||
method: "put",
|
||||
params: {
|
||||
path: {
|
||||
slug,
|
||||
},
|
||||
body: {
|
||||
article,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.sendRequest();
|
||||
|
||||
if (response.result === "success") {
|
||||
redirect(`/article/${response.data.article.slug}`);
|
||||
}
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 422:
|
||||
return submission.reply({
|
||||
formErrors: Object.values(response.error.errors).flat(),
|
||||
});
|
||||
default:
|
||||
throw new Error("api error");
|
||||
}
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { use, useActionState } from "react";
|
||||
import { createArticleAction, updateArticleAction } from "./action";
|
||||
import { ArticleEditor as ArticleEditorPresentation } from "./presentation";
|
||||
import { Article } from "@/utils/types/models";
|
||||
|
||||
export const CreateArticleEditor = () => {
|
||||
const [state, action, isPending] = useActionState(createArticleAction, undefined);
|
||||
|
||||
return <ArticleEditorPresentation result={state} action={action} isPending={isPending} />;
|
||||
};
|
||||
|
||||
export const UpdateArticleEditor = ({ defaultValues }: { defaultValues: Promise<Article> }) => {
|
||||
const [state, action, isPending] = useActionState(updateArticleAction, undefined);
|
||||
|
||||
return (
|
||||
<ArticleEditorPresentation
|
||||
defaultValues={use(defaultValues)}
|
||||
result={state}
|
||||
action={action}
|
||||
isPending={isPending}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./container";
|
||||
@@ -1,11 +0,0 @@
|
||||
.tag-form {
|
||||
& button {
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
outline: inherit;
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { ErrorMessage } from "@/modules/common/components/errorMessage";
|
||||
import { Article } from "@/utils/types/models";
|
||||
import { SubmissionResult, useForm } from "@conform-to/react";
|
||||
import { parseWithZod } from "@conform-to/zod";
|
||||
import { KeyboardEventHandler, startTransition } from "react";
|
||||
import { Tag } from "../tag";
|
||||
import styles from "./presentation.module.css";
|
||||
import { inputsSchema } from "./types";
|
||||
|
||||
type Props = {
|
||||
defaultValues?: Article;
|
||||
action?: (formData: FormData) => void;
|
||||
isPending?: boolean;
|
||||
result?: SubmissionResult<string[]>;
|
||||
};
|
||||
|
||||
export const ArticleEditor = ({ defaultValues, result, action, isPending }: Props) => {
|
||||
const [form, fields] = useForm({
|
||||
lastResult: result,
|
||||
onValidate({ formData }) {
|
||||
return parseWithZod(formData, { schema: inputsSchema });
|
||||
},
|
||||
// onSubmit is defined to avoid resetting form after successful submission
|
||||
// see https://github.com/edmundhung/conform/discussions/606
|
||||
onSubmit(event, { formData }) {
|
||||
event.preventDefault();
|
||||
startTransition(() => {
|
||||
action?.(formData);
|
||||
});
|
||||
},
|
||||
defaultValue: { ...defaultValues },
|
||||
shouldValidate: "onBlur",
|
||||
shouldRevalidate: "onBlur",
|
||||
});
|
||||
|
||||
const tagListErrors = Object.values(fields.tagList.allErrors).flat();
|
||||
|
||||
const onTagFormKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
// prevent form submit
|
||||
event.preventDefault();
|
||||
|
||||
const tag = fields.tag.value;
|
||||
if (!tag) {
|
||||
return;
|
||||
}
|
||||
form.insert({ name: fields.tagList.name, defaultValue: tag });
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const onClickRemoveTag = (index: number) => {
|
||||
form.remove({ name: fields.tagList.name, index });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ErrorMessage messages={form.errors} />
|
||||
<Form id={form.id} action={action} onSubmit={form.onSubmit} noValidate={true}>
|
||||
<Form.Control
|
||||
type="hidden"
|
||||
key={fields.slug.key}
|
||||
name={fields.slug.name}
|
||||
defaultValue={fields.slug.initialValue}
|
||||
/>
|
||||
<Form.Group className="mb-3" controlId="formLastname">
|
||||
<Form.Label>Titre</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
key={fields.title.key}
|
||||
name={fields.title.name}
|
||||
defaultValue={fields.title.initialValue}
|
||||
placeholder="Titre"
|
||||
/>
|
||||
<ErrorMessage messages={fields.title.errors} />
|
||||
</Form.Group>
|
||||
<Form.Group className="mb-3" controlId="formLastname">
|
||||
<Form.Label>Catégorie</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
key={fields.description.key}
|
||||
name={fields.description.name}
|
||||
defaultValue={fields.description.initialValue}
|
||||
placeholder="Catégorie"
|
||||
/>
|
||||
<ErrorMessage messages={fields.description.errors} />
|
||||
</Form.Group>
|
||||
<Form.Group className="mb-3" controlId="formBody">
|
||||
<Form.Label>Description</Form.Label>
|
||||
<Form.Control as="textarea" rows={8}
|
||||
key={fields.body.key}
|
||||
name={fields.body.name}
|
||||
defaultValue={fields.body.initialValue}
|
||||
placeholder="Write your article (in markdown)"
|
||||
/>
|
||||
<ErrorMessage messages={fields.body.errors} />
|
||||
</Form.Group>
|
||||
<Form.Group className="mb-3" controlId="formLastname">
|
||||
<Form.Label>Nom</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
key={fields.tag.key}
|
||||
name={fields.tag.name}
|
||||
defaultValue={fields.tag.initialValue}
|
||||
placeholder="Enter tags"
|
||||
onKeyDown={onTagFormKeyDown}
|
||||
/>
|
||||
<ul className="list-unstyled">
|
||||
{fields.tagList.getFieldList().map((tagField, index) => (
|
||||
<li key={tagField.key}>
|
||||
<Tag component="badge" color="info" className={styles["tag-form"]}>
|
||||
<input type="hidden" name={tagField.name} value={tagField.value} />
|
||||
<span onClick={() => onClickRemoveTag(index)} className="me-2">
|
||||
<i className="icon-close" />
|
||||
</span>
|
||||
{tagField.value}
|
||||
</Tag>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ErrorMessage messages={tagListErrors} />
|
||||
</Form.Group>
|
||||
<Button variant="primary" type="submit" disabled={isPending}>
|
||||
Enregistrer
|
||||
</Button>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const inputsSchema = z.object({
|
||||
slug: z.string().optional(),
|
||||
title: z.string().max(200),
|
||||
description: z.string().max(500),
|
||||
body: z.string().max(2000),
|
||||
tagList: z.array(z.string().max(20)),
|
||||
tag: z.string().max(20).optional(),
|
||||
});
|
||||
|
||||
export type Inputs = z.infer<typeof inputsSchema>;
|
||||
@@ -1,28 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { createApiClient } from "@/utils/api/apiClient";
|
||||
import { getSession } from "@/utils/auth/session";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const deleteArticleAction = async (_prevState: undefined, slug: string) => {
|
||||
if ((await getSession()) == null) {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
const apiClient = createApiClient({
|
||||
path: "/articles/{slug}",
|
||||
method: "delete",
|
||||
params: {
|
||||
path: {
|
||||
slug,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await apiClient.sendRequest();
|
||||
if (response.result === "success") {
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
throw new Error("api error");
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState } from "react";
|
||||
import { deleteArticleAction } from "./action";
|
||||
import { DeleteArticleButton as DeleteArticleButtonPresentation } from "./presentation";
|
||||
|
||||
type Props = {
|
||||
slug: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const DeleteArticleButton = ({ slug, className }: Props) => {
|
||||
const [_state, dispatch, isPending] = useActionState(deleteArticleAction, undefined);
|
||||
|
||||
const action = () => {
|
||||
if (!confirm("Delete this article?")) {
|
||||
return;
|
||||
}
|
||||
dispatch(slug);
|
||||
};
|
||||
|
||||
return <DeleteArticleButtonPresentation action={action} isPending={isPending} className={className} />;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./container";
|
||||
@@ -1,3 +0,0 @@
|
||||
.form {
|
||||
display: inline;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import clsx from "clsx";
|
||||
import { MouseEventHandler } from "react";
|
||||
import styles from "./presentation.module.css";
|
||||
|
||||
type Props = {
|
||||
handleClickDelete?: MouseEventHandler<HTMLButtonElement>;
|
||||
action?: () => void;
|
||||
isPending?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const DeleteArticleButton = ({ action, isPending, className }: Props) => {
|
||||
return (
|
||||
<Form action={action} className={clsx(className, styles["form"])}>
|
||||
<Button variant="danger" type="submit" disabled={isPending}>
|
||||
Supprimer
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { createApiClient } from "@/utils/api/apiClient";
|
||||
import { getSession } from "@/utils/auth/session";
|
||||
import { revalidateTag } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { State } from "./types";
|
||||
|
||||
export const favoriteAction = async (prevState: State, slug: string): Promise<State> => {
|
||||
if ((await getSession()) == null) {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
const apiClient = createApiClient({
|
||||
path: "/articles/{slug}/favorite",
|
||||
method: prevState.favorited ? "delete" : "post",
|
||||
params: {
|
||||
path: {
|
||||
slug,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await apiClient.sendRequest();
|
||||
revalidateTag("");
|
||||
|
||||
if (response.result === "success") {
|
||||
return {
|
||||
favorited: response.data.article.favorited,
|
||||
favoritesCount: response.data.article.favoritesCount,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("api error");
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState } from "react";
|
||||
import { favoriteAction } from "./action";
|
||||
import { FavoriteButton as FavoriteButtonPresentation } from "./presentation";
|
||||
|
||||
type Props = {
|
||||
slug: string;
|
||||
favorited: boolean;
|
||||
favoritesCount: number;
|
||||
showMessage?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const FavoriteButton = ({ slug, favorited, favoritesCount, showMessage, className }: Props) => {
|
||||
const [state, action, isPending] = useActionState(favoriteAction, {
|
||||
favorited,
|
||||
favoritesCount,
|
||||
});
|
||||
|
||||
return (
|
||||
<FavoriteButtonPresentation
|
||||
{...state}
|
||||
action={() => action(slug)}
|
||||
disabled={isPending}
|
||||
showMessage={showMessage}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./container";
|
||||
@@ -1,3 +0,0 @@
|
||||
.form {
|
||||
display: inline;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Form } from "react-bootstrap";
|
||||
import { Button } from "@/modules/common/components/button";
|
||||
import clsx from "clsx";
|
||||
import styles from "./presentation.module.css";
|
||||
|
||||
type Props = {
|
||||
favorited: boolean;
|
||||
favoritesCount: number;
|
||||
action?: () => void;
|
||||
disabled?: boolean;
|
||||
showMessage?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const FavoriteButton = ({ favorited, favoritesCount, action, disabled, showMessage, className }: Props) => {
|
||||
const content = showMessage ? (
|
||||
<>
|
||||
<i className="ion-heart"></i> {favorited ? "Unfavorite Article " : "Favorite Article "}
|
||||
<span className="counter">({favoritesCount})</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<i className="ion-heart"></i> {favoritesCount}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Form action={action} className={clsx(styles["form"], className)}>
|
||||
<Button component="button" variant={favorited ? "filled" : "outline"} disabled={disabled}>
|
||||
{content}
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
export type State = {
|
||||
favorited: boolean;
|
||||
favoritesCount: number;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./tag";
|
||||
@@ -1,94 +0,0 @@
|
||||
import { Badge } from "react-bootstrap";
|
||||
import { ExhaustiveError } from "@/utils/errors";
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
type TagVariant = "outline" | "filled";
|
||||
type TagColor = "primary" | "secondary" | "success" | "danger" | "warning" | "info" | "light" | "dark";
|
||||
|
||||
const variantClassNameMaps: Record<TagVariant, string> = {
|
||||
outline: "tag-outline",
|
||||
filled: "",
|
||||
};
|
||||
|
||||
type CommonTagProps = {
|
||||
variant?: TagVariant;
|
||||
color?: TagColor;
|
||||
};
|
||||
|
||||
//////////////////////////
|
||||
// list item pattern
|
||||
|
||||
type TagPropsAsListItem = CommonTagProps &
|
||||
ComponentPropsWithoutRef<"li"> & {
|
||||
component: "li";
|
||||
};
|
||||
|
||||
const TagAsListItem = ({ children, variant = "filled", className, ...rest }: TagPropsAsListItem) => (
|
||||
<li className={clsx("tag-default tag-pill", className, variantClassNameMaps[variant])} {...rest}>
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
|
||||
//////////////////////////
|
||||
// anchor pattern
|
||||
|
||||
type TagPropsAsAnchor = CommonTagProps &
|
||||
ComponentPropsWithoutRef<"a"> & {
|
||||
component: "a";
|
||||
};
|
||||
|
||||
const TagAsAnchor = ({ children, variant = "filled", href = "", className, ...rest }: TagPropsAsAnchor) => (
|
||||
<Link href={href} className={clsx("tag-default tag-pill", className, variantClassNameMaps[variant])} {...rest}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
|
||||
//////////////////////////
|
||||
// span pattern
|
||||
|
||||
type TagPropsAsSpan = CommonTagProps &
|
||||
ComponentPropsWithoutRef<"span"> & {
|
||||
component: "span";
|
||||
};
|
||||
|
||||
const TagAsSpan = ({ children, variant = "filled", className, ...rest }: TagPropsAsSpan) => (
|
||||
<span className={clsx("tag-default tag-pill", className, variantClassNameMaps[variant])} {...rest}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
//////////////////////////
|
||||
// badge pattern
|
||||
|
||||
type TagPropsAsBadge = CommonTagProps &
|
||||
ComponentPropsWithoutRef<"span"> & {
|
||||
component: "badge";
|
||||
};
|
||||
|
||||
const TagAsBadge = ({ children, color = "primary", className, ...rest }: TagPropsAsBadge) => (
|
||||
<Badge bg={color} {...rest}>
|
||||
{children}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
//////////////////////////
|
||||
|
||||
export type TagProps = TagPropsAsListItem | TagPropsAsAnchor | TagPropsAsSpan | TagPropsAsBadge;
|
||||
|
||||
export const Tag = (props: TagProps) => {
|
||||
switch (props.component) {
|
||||
case "li":
|
||||
return <TagAsListItem {...props} />;
|
||||
case "a":
|
||||
return <TagAsAnchor {...props} />;
|
||||
case "span":
|
||||
return <TagAsSpan {...props} />;
|
||||
case "badge":
|
||||
return <TagAsBadge {...props} />;
|
||||
default:
|
||||
// compilation fails if all cases are not covered
|
||||
throw new ExhaustiveError(props);
|
||||
}
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import { createApiClient } from "@/utils/api/apiClient";
|
||||
import { Article } from "@/utils/types/models";
|
||||
|
||||
export const fetchArticle = async (slug: string) => {
|
||||
const apiClient = createApiClient({
|
||||
path: "/articles/{slug}",
|
||||
method: "get",
|
||||
params: {
|
||||
path: {
|
||||
slug,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await apiClient.sendRequest();
|
||||
|
||||
if (response.result === "success") {
|
||||
return Article.parse(response.data.article);
|
||||
}
|
||||
|
||||
throw new Error("api error.");
|
||||
};
|
||||
@@ -1,136 +0,0 @@
|
||||
import { calcOffsetLimitFromPageNumber } from "@/modules/common/functions/pagination";
|
||||
import { createApiClient } from "@/utils/api/apiClient";
|
||||
import { ArticlePreview } from "@/utils/types/models";
|
||||
|
||||
export const fetchArticlesByAuthor = async (authorUsername: string, pageNumber: number) => {
|
||||
const { offset, limit } = calcOffsetLimitFromPageNumber(pageNumber, 10);
|
||||
|
||||
const client = createApiClient({
|
||||
path: "/articles",
|
||||
method: "get",
|
||||
params: {
|
||||
query: {
|
||||
author: authorUsername,
|
||||
offset,
|
||||
limit,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.sendRequest();
|
||||
|
||||
if (response.result === "success") {
|
||||
return {
|
||||
articles: response.data.articles.map((article) => ArticlePreview.parse(article)),
|
||||
articlesCount: response.data.articlesCount,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("api error.");
|
||||
};
|
||||
|
||||
export const fetchArticlesByTag = async (tag: string, page: number) => {
|
||||
const { offset, limit } = calcOffsetLimitFromPageNumber(page, 10);
|
||||
|
||||
const client = createApiClient({
|
||||
path: "/articles",
|
||||
method: "get",
|
||||
params: {
|
||||
query: {
|
||||
tag,
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.sendRequest();
|
||||
|
||||
if (response.result === "success") {
|
||||
return {
|
||||
articles: response.data.articles.map((article) => ArticlePreview.parse(article)),
|
||||
articlesCount: response.data.articlesCount,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("api error.");
|
||||
};
|
||||
|
||||
export const fetchFavoriteArticles = async (username: string, pageNumber: number) => {
|
||||
const { offset, limit } = calcOffsetLimitFromPageNumber(pageNumber, 10);
|
||||
|
||||
const client = createApiClient({
|
||||
path: "/articles",
|
||||
method: "get",
|
||||
params: {
|
||||
query: {
|
||||
favorited: username,
|
||||
offset,
|
||||
limit,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.sendRequest();
|
||||
|
||||
if (response.result === "success") {
|
||||
return {
|
||||
articles: response.data.articles.map((article) => ArticlePreview.parse(article)),
|
||||
articlesCount: response.data.articlesCount,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("api error.");
|
||||
};
|
||||
|
||||
export const fetchFeedArticles = async (page: number) => {
|
||||
const { offset, limit } = calcOffsetLimitFromPageNumber(page, 10);
|
||||
|
||||
const client = createApiClient({
|
||||
path: "/articles/feed",
|
||||
method: "get",
|
||||
params: {
|
||||
query: {
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.sendRequest();
|
||||
|
||||
if (response.result === "success") {
|
||||
return {
|
||||
articles: response.data.articles.map((article) => ArticlePreview.parse(article)),
|
||||
articlesCount: response.data.articlesCount,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("api error.");
|
||||
};
|
||||
|
||||
export const fetchGlobalArticles = async (page: number) => {
|
||||
const { offset, limit } = calcOffsetLimitFromPageNumber(page, 10);
|
||||
|
||||
const client = createApiClient({
|
||||
path: "/articles",
|
||||
method: "get",
|
||||
params: {
|
||||
query: {
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.sendRequest();
|
||||
|
||||
if (response.result === "success") {
|
||||
return {
|
||||
articles: response.data.articles.map((article) => ArticlePreview.parse(article)),
|
||||
articlesCount: response.data.articlesCount,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("api error.");
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import { createApiClient } from "@/utils/api/apiClient";
|
||||
import { Comment } from "@/utils/types/models";
|
||||
|
||||
export const fetchComments = async (slug: string) => {
|
||||
const client = createApiClient({
|
||||
path: "/articles/{slug}/comments",
|
||||
method: "get",
|
||||
params: {
|
||||
path: {
|
||||
slug,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.sendRequest();
|
||||
|
||||
if (response.result === "success") {
|
||||
return response.data.comments.map((comment) => Comment.parse(comment));
|
||||
}
|
||||
|
||||
throw new Error("api error.");
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
import { createApiClient } from "@/utils/api/apiClient";
|
||||
|
||||
export const fetchTagsList = async () => {
|
||||
const client = createApiClient({
|
||||
path: "/tags",
|
||||
method: "get",
|
||||
params: {},
|
||||
revalidate: 60,
|
||||
});
|
||||
|
||||
const response = await client.sendRequest();
|
||||
|
||||
if (response.result === "success") {
|
||||
return response.data.tags;
|
||||
}
|
||||
|
||||
throw new Error("api error.");
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
import rehypeStringify from "rehype-stringify";
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkRehype from "remark-rehype";
|
||||
import { unified } from "unified";
|
||||
|
||||
export const convertMarkdownToHtml = async (markdown: string) => {
|
||||
const processed = await unified().use(remarkParse).use(remarkRehype).use(rehypeStringify).process(markdown);
|
||||
|
||||
return processed.toString();
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { deleteSession } from "@/utils/auth/session";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const logoutAction = async () => {
|
||||
await deleteSession();
|
||||
redirect("/login");
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import { getSession } from "@/utils/auth/session";
|
||||
import { fetchCurrentUser } from "../../fetch/fetchCurrentUser";
|
||||
import { Header as HeaderPresentation } from "./presentation";
|
||||
|
||||
export const Header = async () => {
|
||||
const session = await getSession();
|
||||
|
||||
if (session == null) {
|
||||
return <HeaderPresentation authUser={undefined} />;
|
||||
}
|
||||
|
||||
const user = await fetchCurrentUser();
|
||||
return <HeaderPresentation authUser={user} />;
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useSelectedLayoutSegment } from "next/navigation";
|
||||
import { ReactNode } from "react";
|
||||
import { Nav } from "react-bootstrap";
|
||||
|
||||
export const HeaderMenuItem = ({
|
||||
href,
|
||||
children,
|
||||
segment,
|
||||
}: {
|
||||
href: string;
|
||||
children: ReactNode;
|
||||
segment: string | null;
|
||||
}) => {
|
||||
const currentSegment = useSelectedLayoutSegment();
|
||||
const isActive = currentSegment === segment;
|
||||
|
||||
return (
|
||||
<Nav.Link className={clsx("nav-link", isActive && "active")} href={href}>
|
||||
{children}
|
||||
</Nav.Link>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./container";
|
||||
@@ -1,73 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { User } from "@/utils/types/models";
|
||||
import { Button, Form, Container, Nav, Navbar, NavDropdown } from "react-bootstrap";
|
||||
import { HeaderMenuItem } from "./headerMenuItem";
|
||||
import LogoSmall from "@/app/_components/layoutElements/logoSmall";
|
||||
import { LogoutButton } from "@/modules/features/auth/components/logoutButton";
|
||||
import ButtonOffcanvas from "@/modules/common/components/button/buttonOffcanvas";
|
||||
|
||||
|
||||
const UnauthenticatedMenus = () => {
|
||||
return (
|
||||
<>
|
||||
<HeaderMenuItem href="/login" segment="login">
|
||||
Se connecter
|
||||
</HeaderMenuItem>
|
||||
<Navbar.Text>
|
||||
/
|
||||
</Navbar.Text>
|
||||
<HeaderMenuItem href="/register" segment="register">
|
||||
Créer un compte
|
||||
</HeaderMenuItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AuthenticatedMenus = ({ authUser }: { authUser: User }) => {
|
||||
return (
|
||||
<>
|
||||
<NavDropdown title={authUser.username} id="basic-nav-dropdown">
|
||||
<NavDropdown.Item href={`/profile/${authUser.username}`}>
|
||||
<span className="icon-user me-2"></span>Mon compte
|
||||
</NavDropdown.Item>
|
||||
<NavDropdown.Item href="/settings">
|
||||
<span className="icon-cog me-2"></span>Paramètres
|
||||
</NavDropdown.Item>
|
||||
<NavDropdown.Item href="/editor">
|
||||
<span className="icon-barcode me-2"></span>Ajouter un produit
|
||||
</NavDropdown.Item>
|
||||
<NavDropdown.Divider />
|
||||
<NavDropdown.Item href="/logout">
|
||||
<span className="icon-switch me-2"></span>Se déconnecter
|
||||
</NavDropdown.Item>
|
||||
</NavDropdown>
|
||||
<ButtonOffcanvas className="ms-3" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Header = ({ authUser }: { authUser?: User }) => {
|
||||
return (
|
||||
<header>
|
||||
<Navbar bg="dark" data-bs-theme="dark" expand="lg" className="bg-dark-blue mb-4 pt-1 pb-2">
|
||||
<Container>
|
||||
<Navbar.Brand href="/">
|
||||
<LogoSmall />
|
||||
</Navbar.Brand>
|
||||
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||
<Navbar.Collapse id="basic-navbar-nav" className="mt-3">
|
||||
<Nav className="me-auto">
|
||||
<Nav.Link href="/fleurs">Fleurs</Nav.Link>
|
||||
<Nav.Link href="/resines">Résines</Nav.Link>
|
||||
<Nav.Link href="/molecules">Molécules</Nav.Link>
|
||||
</Nav>
|
||||
<Nav className="d-flex">
|
||||
{authUser ? <AuthenticatedMenus authUser={authUser} /> : <UnauthenticatedMenus />}
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</Container>
|
||||
</Navbar>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { deleteSession } from "@/utils/auth/session";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const logoutAction = async () => {
|
||||
await deleteSession();
|
||||
redirect("/login");
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState } from "react";
|
||||
import { logoutAction } from "./action";
|
||||
import { LogoutButton as LogoutButtonPresentation } from "./presentation";
|
||||
|
||||
type Variant = "primary" | "secondary" | "success" | "danger" | "warning" | "info" | "light" | "dark"
|
||||
| "outline-primary" | "outline-secondary" | "outline-success" | "outline-danger" | "outline-warning" | "outline-info" | "outline-light" | "outline-dark";
|
||||
const DEFAULT_VARIANT: Variant = "danger";
|
||||
|
||||
type CommonProps = {
|
||||
variant?: Variant;
|
||||
};
|
||||
|
||||
export const LogoutButton = ({variant = DEFAULT_VARIANT}: CommonProps) => {
|
||||
const [_state, action, isPending] = useActionState(logoutAction, undefined);
|
||||
|
||||
return <LogoutButtonPresentation variant={variant} action={action} isPending={isPending} />;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./container";
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
|
||||
type Props = { action?: () => void; isPending?: boolean; variant?: string; };
|
||||
|
||||
export const LogoutButton = ({ action, isPending, variant }: Props) => {
|
||||
return (
|
||||
<Form action={action}>
|
||||
<Button variant={variant} type="submit" disabled={isPending}>
|
||||
<i className="icon-switch me-2" />Se déconnecter
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||