Initial commit
0
app/assets/builds/.keep
Normal file
BIN
app/assets/fonts/.DS_Store
vendored
Normal file
BIN
app/assets/fonts/generalsans/GeneralSans-Variable.woff2
Normal file
0
app/assets/images/.keep
Normal file
3
app/assets/images/icon-add.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 3C10.2652 3 10.5196 3.10536 10.7071 3.29289C10.8946 3.48043 11 3.73478 11 4V9H16C16.2652 9 16.5196 9.10536 16.7071 9.29289C16.8946 9.48043 17 9.73478 17 10C17 10.2652 16.8946 10.5196 16.7071 10.7071C16.5196 10.8946 16.2652 11 16 11H11V16C11 16.2652 10.8946 16.5196 10.7071 16.7071C10.5196 16.8946 10.2652 17 10 17C9.73478 17 9.48043 16.8946 9.29289 16.7071C9.10536 16.5196 9 16.2652 9 16V11H4C3.73478 11 3.48043 10.8946 3.29289 10.7071C3.10536 10.5196 3 10.2652 3 10C3 9.73478 3.10536 9.48043 3.29289 9.29289C3.48043 9.10536 3.73478 9 4 9H9V4C9 3.73478 9.10536 3.48043 9.29289 3.29289C9.48043 3.10536 9.73478 3 10 3Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 777 B |
3
app/assets/images/icon-bank-accounts.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.25 9.00147V7.00147C17.25 6.47103 17.0393 5.96232 16.6642 5.58725C16.2891 5.21218 15.7804 5.00146 15.25 5.00146H5.25C4.71957 5.00146 4.21086 5.21218 3.83579 5.58725C3.46071 5.96232 3.25 6.47103 3.25 7.00147V13.0015C3.25 13.5319 3.46071 14.0406 3.83579 14.4157C4.21086 14.7908 4.71957 15.0015 5.25 15.0015H7.25M9.25 19.0015H19.25C19.7804 19.0015 20.2891 18.7908 20.6642 18.4157C21.0393 18.0406 21.25 17.5319 21.25 17.0015V11.0015C21.25 10.471 21.0393 9.96233 20.6642 9.58725C20.2891 9.21218 19.7804 9.00147 19.25 9.00147H9.25C8.71957 9.00147 8.21086 9.21218 7.83579 9.58725C7.46071 9.96233 7.25 10.471 7.25 11.0015V17.0015C7.25 17.5319 7.46071 18.0406 7.83579 18.4157C8.21086 18.7908 8.71957 19.0015 9.25 19.0015ZM16.25 14.0015C16.25 14.5319 16.0393 15.0406 15.6642 15.4157C15.2891 15.7908 14.7804 16.0015 14.25 16.0015C13.7196 16.0015 13.2109 15.7908 12.8358 15.4157C12.4607 15.0406 12.25 14.5319 12.25 14.0015C12.25 13.471 12.4607 12.9623 12.8358 12.5873C13.2109 12.2122 13.7196 12.0015 14.25 12.0015C14.7804 12.0015 15.2891 12.2122 15.6642 12.5873C16.0393 12.9623 16.25 13.471 16.25 14.0015Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
3
app/assets/images/icon-credit-card.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.25 8.25H21.75M2.25 9H21.75M4.5 19.5H19.5C20.0967 19.5 20.669 19.2629 21.091 18.841C21.5129 18.419 21.75 17.8467 21.75 17.25V6.75C21.75 6.15326 21.5129 5.58097 21.091 5.15901C20.669 4.73705 20.0967 4.5 19.5 4.5H4.5C3.90326 4.5 3.33097 4.73705 2.90901 5.15901C2.48705 5.58097 2.25 6.15326 2.25 6.75V17.25C2.25 17.8467 2.48705 18.419 2.90901 18.841C3.33097 19.2629 3.90326 19.5 4.5 19.5Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 567 B |
3
app/assets/images/icon-dashboard.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.125 5C3.125 4.50272 3.32254 4.02581 3.67417 3.67417C4.02581 3.32254 4.50272 3.125 5 3.125H6.875C7.37228 3.125 7.84919 3.32254 8.20082 3.67417C8.55246 4.02581 8.75 4.50272 8.75 5V6.875C8.75 7.37228 8.55246 7.84919 8.20082 8.20082C7.84919 8.55246 7.37228 8.75 6.875 8.75H5C4.50272 8.75 4.02581 8.55246 3.67417 8.20082C3.32254 7.84919 3.125 7.37228 3.125 6.875V5ZM3.125 13.125C3.125 12.6277 3.32254 12.1508 3.67417 11.7992C4.02581 11.4475 4.50272 11.25 5 11.25H6.875C7.37228 11.25 7.84919 11.4475 8.20082 11.7992C8.55246 12.1508 8.75 12.6277 8.75 13.125V15C8.75 15.4973 8.55246 15.9742 8.20082 16.3258C7.84919 16.6775 7.37228 16.875 6.875 16.875H5C4.50272 16.875 4.02581 16.6775 3.67417 16.3258C3.32254 15.9742 3.125 15.4973 3.125 15V13.125ZM11.25 5C11.25 4.50272 11.4475 4.02581 11.7992 3.67417C12.1508 3.32254 12.6277 3.125 13.125 3.125H15C15.4973 3.125 15.9742 3.32254 16.3258 3.67417C16.6775 4.02581 16.875 4.50272 16.875 5V6.875C16.875 7.37228 16.6775 7.84919 16.3258 8.20082C15.9742 8.55246 15.4973 8.75 15 8.75H13.125C12.6277 8.75 12.1508 8.55246 11.7992 8.20082C11.4475 7.84919 11.25 7.37228 11.25 6.875V5ZM11.25 13.125C11.25 12.6277 11.4475 12.1508 11.7992 11.7992C12.1508 11.4475 12.6277 11.25 13.125 11.25H15C15.4973 11.25 15.9742 11.4475 16.3258 11.7992C16.6775 12.1508 16.875 12.6277 16.875 13.125V15C16.875 15.4973 16.6775 15.9742 16.3258 16.3258C15.9742 16.6775 15.4973 16.875 15 16.875H13.125C12.6277 16.875 12.1508 16.6775 11.7992 16.3258C11.4475 15.9742 11.25 15.4973 11.25 15V13.125Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
3
app/assets/images/icon-investments.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.7998 4.80005V15.6C4.7998 17.2569 6.14295 18.6 7.7998 18.6H19.6998M8.3998 13.2L11.6998 9.73338L15.5331 12.8L19.6998 7.86672" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 305 B |
3
app/assets/images/icon-real-estate.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.25 11.9984L11.204 3.04336C11.644 2.60436 12.356 2.60436 12.795 3.04336L21.75 11.9984M4.5 9.74836V19.8734C4.5 20.4944 5.004 20.9984 5.625 20.9984H9.75V16.1234C9.75 15.5024 10.254 14.9984 10.875 14.9984H13.125C13.746 14.9984 14.25 15.5024 14.25 16.1234V20.9984H18.375C18.996 20.9984 19.5 20.4944 19.5 19.8734V9.74836M8.25 20.9984H16.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 515 B |
7
app/assets/images/logo.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<svg width="94" height="20" viewBox="0 0 94 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.3019 3.39623C20.761 3.39623 21.956 3.84277 22.8868 4.73585C23.8302 5.61635 24.3019 6.98742 24.3019 8.84906V15.0943H19.9623V9.5283C19.9623 8.59748 19.7547 7.9434 19.3396 7.56604C18.9245 7.1761 18.2642 6.98113 17.3585 6.98113C16.4528 6.98113 15.717 7.18239 15.1509 7.58491C14.5975 7.98742 14.3208 8.63522 14.3208 9.5283V15.0943H9.98113V9.5283C9.98113 8.59748 9.77359 7.9434 9.35849 7.56604C8.9434 7.1761 8.28302 6.98113 7.37736 6.98113C6.4717 6.98113 5.73585 7.18239 5.16981 7.58491C4.61635 7.98742 4.33962 8.63522 4.33962 9.5283V15.0943H0V3.77358H4.33962V5.77358C5.63522 4.18868 7.2956 3.39623 9.32076 3.39623C10.3899 3.39623 11.327 3.63522 12.1321 4.11321C12.9497 4.57862 13.5409 5.30818 13.9057 6.30189C14.5723 5.37107 15.3459 4.65409 16.2264 4.15094C17.1069 3.6478 18.1321 3.39623 19.3019 3.39623Z" fill="#242629"/>
|
||||
<path d="M34.7652 3.39623C36.3752 3.39623 37.6897 3.59119 38.7086 3.98113C39.74 4.37107 40.5073 5 41.0105 5.86792C41.5262 6.72327 41.7903 7.86792 41.8029 9.30189V15.0943H37.4633V13.5849C36.5199 14.1635 35.4759 14.5912 34.3312 14.8679C33.1991 15.1447 32.0985 15.283 31.0293 15.283C29.4319 15.283 28.1614 15 27.218 14.434C26.2746 13.8679 25.8029 12.9497 25.8029 11.6792C25.8029 10.2704 26.4256 9.27673 27.6708 8.69811C28.9161 8.10692 30.4633 7.81132 32.3124 7.81132C33.9098 7.81132 35.6268 8 37.4633 8.37736C37.413 7.6478 37.0859 7.13836 36.4822 6.84906C35.891 6.55975 35.0482 6.41509 33.9539 6.41509C32.9602 6.41509 31.9476 6.49057 30.9161 6.64151C29.8973 6.77987 28.7023 7.00629 27.3312 7.32075L26.7274 4.35849C29.608 3.71698 32.2872 3.39623 34.7652 3.39623ZM32.5199 12.2642C33.5513 12.2642 35.1992 12.0755 37.4633 11.6981V10.8302C35.7023 10.5786 34.1991 10.4528 32.9539 10.4528C31.9979 10.4528 31.3186 10.522 30.9161 10.6604C30.5262 10.7987 30.3312 11.0377 30.3312 11.3774C30.3312 11.7044 30.4947 11.9371 30.8218 12.0755C31.1488 12.2013 31.7149 12.2642 32.5199 12.2642Z" fill="#242629"/>
|
||||
<path d="M55.0136 3.77358H59.4098L54.089 14.8113C53.523 15.9811 52.9381 16.9371 52.3343 17.6792C51.7431 18.434 51.0387 19.0063 50.2211 19.3962C49.4035 19.7987 48.4287 20 47.2966 20C46.6802 20 45.9947 19.9434 45.24 19.8302C44.4853 19.717 43.7431 19.5535 43.0136 19.3396V16.0377C44.6991 16.4151 45.9129 16.6038 46.6551 16.6038C47.196 16.6038 47.6173 16.5723 47.9192 16.5094C48.2337 16.4465 48.5104 16.3082 48.7494 16.0943C49.001 15.8805 49.2588 15.5472 49.523 15.0943H47.4853L42.0702 3.77358H46.4664L50.7305 12.6792L55.0136 3.77358Z" fill="#242629"/>
|
||||
<path d="M70.216 3.39623C71.2851 3.39623 72.2851 3.59748 73.216 4C74.1468 4.38994 74.9078 5.03774 75.499 5.9434C76.1027 6.83648 76.4046 8 76.4046 9.43396C76.4046 10.8679 76.1027 12.0377 75.499 12.9434C74.9078 13.8365 74.1468 14.4843 73.216 14.8868C72.2851 15.2767 71.2851 15.4717 70.216 15.4717C69.109 15.4717 68.0902 15.283 67.1594 14.9057C66.2411 14.5157 65.5116 13.9057 64.9707 13.0755V15.0943H60.631V0H64.9707V5.79245C65.5116 4.96226 66.2411 4.35849 67.1594 3.98113C68.0902 3.59119 69.109 3.39623 70.216 3.39623ZM68.4235 11.8868C69.5807 11.8868 70.4424 11.6855 71.0084 11.283C71.587 10.8805 71.8763 10.2642 71.8763 9.43396C71.8763 8.60377 71.587 7.98742 71.0084 7.58491C70.4424 7.18239 69.5807 6.98113 68.4235 6.98113C67.2663 6.98113 66.3983 7.18239 65.8197 7.58491C65.2537 7.98742 64.9707 8.60377 64.9707 9.43396C64.9707 10.2642 65.2537 10.8805 65.8197 11.283C66.3983 11.6855 67.2663 11.8868 68.4235 11.8868Z" fill="#242629"/>
|
||||
<path d="M86.1885 12.2642C87.1948 12.2642 87.9558 12.1635 88.4715 11.9623C88.9999 11.7484 89.3646 11.4403 89.5659 11.0377H93.8112C93.4212 12.4969 92.5973 13.6038 91.3395 14.3585C90.0942 15.1006 88.3206 15.4717 86.0187 15.4717C83.4275 15.4717 81.4087 14.956 79.9621 13.9245C78.5156 12.8931 77.7923 11.3962 77.7923 9.43396C77.7923 8.12579 78.1319 7.02516 78.8112 6.13208C79.4904 5.22641 80.4338 4.54717 81.6414 4.09434C82.8489 3.62893 84.2514 3.39623 85.8489 3.39623C88.3521 3.39623 90.3206 3.96226 91.7546 5.09434C93.1885 6.22642 93.9055 7.91195 93.9055 10.1509H82.1885C82.3269 10.9308 82.7231 11.478 83.3772 11.7925C84.0439 12.1069 84.981 12.2642 86.1885 12.2642ZM85.8489 6.22642C84.8929 6.22642 84.1256 6.36478 83.547 6.64151C82.9684 6.91824 82.5659 7.37107 82.3395 8H89.415C89.176 7.37107 88.7609 6.91824 88.1697 6.64151C87.591 6.36478 86.8175 6.22642 85.8489 6.22642Z" fill="#242629"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
19
app/assets/images/logomark.svg
Normal file
|
@ -0,0 +1,19 @@
|
|||
<svg width="371" height="233" viewBox="0 0 371 233" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2_280)">
|
||||
<path d="M133.619 43.4224H84.7752C72.7844 43.4224 63.064 33.702 63.064 21.7115C63.064 9.72063 72.7844 -0.000171661 84.7752 -0.000171661H133.619C145.61 -0.000171661 155.33 9.72063 155.33 21.7115C155.33 33.702 145.61 43.4224 133.619 43.4224Z" fill="#19181D"/>
|
||||
<path d="M285.312 43.4224H236.468C224.477 43.4224 214.757 33.702 214.757 21.7115C214.757 9.72063 224.477 -0.000171661 236.468 -0.000171661H285.312C297.303 -0.000171661 307.023 9.72063 307.023 21.7115C307.023 33.702 297.303 43.4224 285.312 43.4224Z" fill="#19181D"/>
|
||||
<path d="M156.562 105.917H64.0199C52.029 105.917 42.3086 96.1961 42.3086 84.2056C42.3086 72.2148 52.029 62.494 64.0199 62.494H156.562C168.553 62.494 178.273 72.2148 178.273 84.2056C178.273 96.1961 168.553 105.917 156.562 105.917Z" fill="#19181D"/>
|
||||
<path d="M219.509 105.917H306.135C318.126 105.917 327.846 96.1961 327.846 84.2056C327.846 72.2148 318.126 62.494 306.135 62.494H219.509C207.518 62.494 197.797 72.2148 197.797 84.2056C197.797 96.1961 207.518 105.917 219.509 105.917Z" fill="#19181D"/>
|
||||
<path d="M72.3277 232.781H21.7109C9.72041 232.781 0 223.06 0 211.069C0 199.079 9.72041 189.358 21.7109 189.358H72.3277C84.3186 189.358 94.039 199.079 94.039 211.069C94.039 223.06 84.3186 232.781 72.3277 232.781Z" fill="#19181D"/>
|
||||
<path d="M298.672 232.781H349.289C361.279 232.781 371 223.06 371 211.069C371 199.079 361.279 189.358 349.289 189.358H298.672C286.681 189.358 276.96 199.079 276.96 211.069C276.96 223.06 286.681 232.781 298.672 232.781Z" fill="#19181D"/>
|
||||
<path d="M205.961 232.641H164.761C152.77 232.641 143.05 222.921 143.05 210.93C143.05 198.939 152.77 189.219 164.761 189.219H205.961C217.952 189.219 227.672 198.939 227.672 210.93C227.672 222.921 217.952 232.641 205.961 232.641Z" fill="#19181D"/>
|
||||
<path d="M91.1608 170.293H42.4272C30.4367 170.293 20.7163 160.573 20.7163 148.583C20.7163 136.592 30.4367 126.871 42.4272 126.871H91.1608C103.152 126.871 112.872 136.592 112.872 148.583C112.872 160.573 103.152 170.293 91.1608 170.293Z" fill="#19181D"/>
|
||||
<path d="M278.994 170.293H327.728C339.718 170.293 349.438 160.573 349.438 148.583C349.438 136.592 339.718 126.871 327.728 126.871H278.994C267.003 126.871 257.283 136.592 257.283 148.583C257.283 160.573 267.003 170.293 278.994 170.293Z" fill="#19181D"/>
|
||||
<path d="M221.622 170.155H149.099C137.109 170.155 127.388 160.434 127.388 148.444C127.388 136.453 137.109 126.732 149.099 126.732H221.622C233.612 126.732 243.333 136.453 243.333 148.444C243.333 160.434 233.612 170.155 221.622 170.155Z" fill="#19181D"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2_280">
|
||||
<rect width="371" height="233" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
1
app/assets/stylesheets/application.css
Normal file
|
@ -0,0 +1 @@
|
|||
/* Application styles */
|
45
app/assets/stylesheets/application.tailwind.css
Normal file
|
@ -0,0 +1,45 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.prose table {
|
||||
@apply divide-y divide-gray-300;
|
||||
}
|
||||
|
||||
.prose tr {
|
||||
@apply divide-x divide-gray-100;
|
||||
}
|
||||
|
||||
.prose th {
|
||||
@apply whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900;
|
||||
}
|
||||
|
||||
.prose tbody {
|
||||
@apply divide-y divide-gray-200;
|
||||
}
|
||||
|
||||
.prose td {
|
||||
@apply px-2 py-2 text-sm text-gray-500 whitespace-nowrap;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
@apply relative p-4 bg-gray-100 border border-gray-200 rounded-2xl focus-within:bg-white focus-within:drop-shadow-form focus-within:opacity-100;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
@apply block text-sm font-medium text-gray-500;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
@apply p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
@font-face {
|
||||
font-family: "GeneralSans";
|
||||
font-style: normal;
|
||||
font-weight: 200 700;
|
||||
font-display: swap;
|
||||
src: url("generalsans/GeneralSans-Variable.woff2") format("woff2");
|
||||
}
|
||||
}
|
4
app/channels/application_cable/channel.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
module ApplicationCable
|
||||
class Channel < ActionCable::Channel::Base
|
||||
end
|
||||
end
|
4
app/channels/application_cable/connection.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
end
|
||||
end
|
10
app/controllers/accounts_controller.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class AccountsController < ApplicationController
|
||||
def index
|
||||
end
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
end
|
35
app/controllers/application_controller.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
class ApplicationController < ActionController::Base
|
||||
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
|
||||
allow_browser versions: :modern
|
||||
|
||||
private
|
||||
|
||||
def authenticate_user!
|
||||
redirect_to new_session_path unless user_signed_in?
|
||||
end
|
||||
|
||||
def current_user
|
||||
Current.user || authenticate_user_from_session
|
||||
end
|
||||
helper_method :current_user
|
||||
|
||||
def authenticate_user_from_session
|
||||
User.find_by(id: session[:user_id])
|
||||
end
|
||||
|
||||
def user_signed_in?
|
||||
current_user.present?
|
||||
end
|
||||
helper_method :user_signed_in?
|
||||
|
||||
def login(user)
|
||||
Current.user = user
|
||||
reset_session
|
||||
session[:user_id] = user.id
|
||||
end
|
||||
|
||||
def logout
|
||||
Current.user = nil
|
||||
reset_session
|
||||
end
|
||||
end
|
0
app/controllers/concerns/.keep
Normal file
6
app/controllers/pages_controller.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
class PagesController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def dashboard
|
||||
end
|
||||
end
|
40
app/controllers/password_resets_controller.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
class PasswordResetsController < ApplicationController
|
||||
layout "auth"
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
if (user = User.find_by(email: params[:email]))
|
||||
PasswordMailer.with(
|
||||
user: user,
|
||||
token: user.generate_token_for(:password_reset)
|
||||
).password_reset.deliver_later
|
||||
end
|
||||
|
||||
redirect_to root_path, notice: "If an account with that email exists, we have sent a link to reset your password."
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if @user.update(password_params)
|
||||
redirect_to new_session_path, notice: "Your password has been reset."
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user_by_token
|
||||
@user = User.find_by_token_for(password_reset: params[:token])
|
||||
redirect_to new_password_reset_path, alert: "Invalid token." unless @user.present?
|
||||
end
|
||||
|
||||
def password_params
|
||||
params.require(:user).permit(:password, :password_confirmation)
|
||||
end
|
||||
end
|
||||
|
21
app/controllers/passwords_controller.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class PasswordsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if current_user.update(password_params)
|
||||
redirect_to root_path, notice: "Your password has been updated successfully."
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def password_params
|
||||
params.require(:user).permit(:password, :password_confirmation, :password_challenge).with_defaults(password_challenge: "")
|
||||
end
|
||||
end
|
||||
|
30
app/controllers/registrations_controller.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
class RegistrationsController < ApplicationController
|
||||
layout "auth"
|
||||
|
||||
def new
|
||||
@user = User.new
|
||||
end
|
||||
|
||||
def create
|
||||
@user = User.new(user_params)
|
||||
|
||||
family = Family.new
|
||||
@user.family = family
|
||||
|
||||
if @user.save
|
||||
login @user
|
||||
flash[:notice] = "You have signed up successfully."
|
||||
redirect_to root_path
|
||||
else
|
||||
flash[:alert] = "Invalid input, please try again."
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:name, :email, :password, :password_confirmation)
|
||||
end
|
||||
end
|
||||
|
22
app/controllers/sessions_controller.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
class SessionsController < ApplicationController
|
||||
layout "auth"
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
if user = User.authenticate_by(email: params[:email], password: params[:password])
|
||||
login user
|
||||
redirect_to root_path
|
||||
else
|
||||
flash.now[:alert] = "Invalid email or password."
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
logout
|
||||
redirect_to root_path, notice: "You have signed out successfully."
|
||||
end
|
||||
end
|
||||
|
2
app/helpers/accounts_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module AccountsHelper
|
||||
end
|
9
app/helpers/application_helper.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
module ApplicationHelper
|
||||
def title(page_title)
|
||||
content_for(:title) { page_title }
|
||||
end
|
||||
|
||||
def header_title(page_title)
|
||||
content_for(:header_title) { page_title }
|
||||
end
|
||||
end
|
2
app/helpers/pages_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module PagesHelper
|
||||
end
|
3
app/javascript/application.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
|
||||
import "@hotwired/turbo-rails"
|
||||
import "controllers"
|
9
app/javascript/controllers/application.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { Application } from "@hotwired/stimulus"
|
||||
|
||||
const application = Application.start()
|
||||
|
||||
// Configure Stimulus development experience
|
||||
application.debug = false
|
||||
window.Stimulus = application
|
||||
|
||||
export { application }
|
23
app/javascript/controllers/dropdown_controller.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
// Connects to data-controller="dropdown"
|
||||
export default class extends Controller {
|
||||
static targets = ["menu"]
|
||||
|
||||
toggleMenu(event) {
|
||||
event.stopPropagation(); // Prevent event from closing the menu immediately
|
||||
this.menuTarget.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
hideMenu() {
|
||||
this.menuTarget.classList.add("hidden");
|
||||
}
|
||||
|
||||
connect() {
|
||||
document.addEventListener("click", () => this.hideMenu());
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
document.removeEventListener("click", () => this.hideMenu());
|
||||
}
|
||||
}
|
7
app/javascript/controllers/hello_controller.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.element.textContent = "Hello World!"
|
||||
}
|
||||
}
|
11
app/javascript/controllers/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Import and register all your controllers from the importmap under controllers/*
|
||||
|
||||
import { application } from "controllers/application"
|
||||
|
||||
// Eager load all controllers defined in the import map under controllers/**/*_controller
|
||||
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
|
||||
eagerLoadControllersFrom("controllers", application)
|
||||
|
||||
// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
|
||||
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
|
||||
// lazyLoadControllersFrom("controllers", application)
|
7
app/jobs/application_job.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class ApplicationJob < ActiveJob::Base
|
||||
# Automatically retry jobs that encountered a deadlock
|
||||
# retry_on ActiveRecord::Deadlocked
|
||||
|
||||
# Most jobs are safe to ignore if the underlying records are no longer available
|
||||
# discard_on ActiveJob::DeserializationError
|
||||
end
|
4
app/mailers/application_mailer.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
class ApplicationMailer < ActionMailer::Base
|
||||
default from: "from@example.com"
|
||||
layout "mailer"
|
||||
end
|
5
app/mailers/password_mailer.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class PasswordMailer < ApplicationMailer
|
||||
def password_reset
|
||||
mail to: params[:user].email
|
||||
end
|
||||
end
|
3
app/models/account.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Account < ApplicationRecord
|
||||
belongs_to :family
|
||||
end
|
3
app/models/application_record.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class ApplicationRecord < ActiveRecord::Base
|
||||
primary_abstract_class
|
||||
end
|
0
app/models/concerns/.keep
Normal file
3
app/models/current.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Current < ActiveSupport::CurrentAttributes
|
||||
attribute :user
|
||||
end
|
4
app/models/family.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
class Family < ApplicationRecord
|
||||
has_many :users, dependent: :destroy
|
||||
has_many :accounts, dependent: :destroy
|
||||
end
|
12
app/models/user.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
class User < ApplicationRecord
|
||||
has_secure_password
|
||||
|
||||
belongs_to :family
|
||||
|
||||
validates :email, presence: true, uniqueness: true
|
||||
normalizes :email, with: -> (email) { email.strip.downcase }
|
||||
|
||||
generates_token_for :password_reset, expires_in: 15.minutes do
|
||||
password_salt&.last(10)
|
||||
end
|
||||
end
|
4
app/views/accounts/index.html.erb
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div>
|
||||
<h1 class="font-bold text-4xl">Accounts#index</h1>
|
||||
<p>Find me in app/views/accounts/index.html.erb</p>
|
||||
</div>
|
43
app/views/accounts/new.html.erb
Normal file
|
@ -0,0 +1,43 @@
|
|||
<h1 class="text-3xl font-semibold font-display">Add an account</h1>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mt-8 text-sm sm:grid-cols-3 md:grid-cols-4">
|
||||
<div class="relative flex-col items-center px-5 py-4 space-x-3 text-center bg-white border border-gray-100 shadow-sm rounded-xl hover:border-gray-200">
|
||||
<%= link_to "new_bank_path", class: "flex flex-col items-center justify-center w-full text-center focus:outline-none" do %>
|
||||
<span class="absolute inset-0" aria-hidden="true"></span>
|
||||
<span class="flex w-10 h-10 shrink-0 grow-0 items-center justify-center rounded-xl bg-[#EAF4FF] mb-2">
|
||||
<%= inline_svg_tag('icon-bank-accounts.svg', class: 'text-[#3492FB] stroke-current') %>
|
||||
</span>
|
||||
Bank accounts
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="relative flex-col items-center px-5 py-4 space-x-3 text-center bg-white border border-gray-100 shadow-sm rounded-xl hover:border-gray-200">
|
||||
<%= link_to "new_investment_path", class: "flex flex-col items-center justify-center w-full text-center focus:outline-none" do %>
|
||||
<span class="absolute inset-0" aria-hidden="true"></span>
|
||||
<span class="flex w-10 h-10 shrink-0 grow-0 items-center justify-center rounded-xl bg-[#EDF7F4] mb-2">
|
||||
<%= inline_svg_tag('icon-investments.svg', class: 'text-[#1BD5A1] stroke-current') %>
|
||||
</span>
|
||||
Investments
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="relative flex-col items-center px-5 py-4 space-x-3 text-center bg-white border border-gray-100 shadow-sm rounded-xl hover:border-gray-200">
|
||||
<%= link_to "new_credit_path", class: "flex flex-col items-center justify-center w-full text-center focus:outline-none" do %>
|
||||
<span class="absolute inset-0" aria-hidden="true"></span>
|
||||
<span class="flex w-10 h-10 shrink-0 grow-0 items-center justify-center rounded-xl bg-[#E6F6FA] mb-2">
|
||||
<%= inline_svg_tag('icon-credit-card.svg', class: 'text-[#189FC7] stroke-current') %>
|
||||
</span>
|
||||
Credit cards
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="relative flex-col items-center px-5 py-4 space-x-3 text-center bg-white border border-gray-100 shadow-sm rounded-xl hover:border-gray-200">
|
||||
<%= link_to "new_real_estate_path", class: "flex flex-col items-center justify-center w-full text-center focus:outline-none" do %>
|
||||
<span class="absolute inset-0" aria-hidden="true"></span>
|
||||
<span class="flex w-10 h-10 shrink-0 grow-0 items-center justify-center rounded-xl bg-[#FEF0F7] mb-2">
|
||||
<%= inline_svg_tag('icon-real-estate.svg', class: 'text-[#F03695] stroke-current') %>
|
||||
</span>
|
||||
Real estate
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
4
app/views/accounts/show.html.erb
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div>
|
||||
<h1 class="font-bold text-4xl">Accounts#show</h1>
|
||||
<p>Find me in app/views/accounts/show.html.erb</p>
|
||||
</div>
|
69
app/views/layouts/application.html.erb
Normal file
|
@ -0,0 +1,69 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="bg-offwhite">
|
||||
<head>
|
||||
<title><%= content_for(:title) || "Maybe" %></title>
|
||||
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="Maybe">
|
||||
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
|
||||
<%= yield :head %>
|
||||
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="icon" href="/icon.png" type="image/png">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/icon.png">
|
||||
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
|
||||
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
||||
<%= javascript_importmap_tags %>
|
||||
<%= hotwire_livereload_tags if Rails.env.development? %>
|
||||
</head>
|
||||
|
||||
<body class="h-full">
|
||||
<div class="flex">
|
||||
<div class="flex-col p-5 min-w-80">
|
||||
<div class="flex items-center justify-between">
|
||||
<%= link_to root_path do %>
|
||||
<%= image_tag 'logo.svg', alt: 'Maybe' %>
|
||||
<% end %>
|
||||
<div class="relative" data-controller="dropdown">
|
||||
<div class="flex" data-action="click->dropdown#toggleMenu">
|
||||
<div class="mr-1.5 text-white w-8 h-8 bg-gray-400 rounded-full flex items-center justify-center text-lg uppercase"><%= current_user.email.first %></div>
|
||||
</div>
|
||||
|
||||
<div class="absolute z-10 hidden w-screen px-2 mt-2 -translate-x-1/2 left-1/2 max-w-min" data-dropdown-target="menu">
|
||||
<div class="w-48 px-3 text-sm font-semibold leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= button_to "Log Out", session_path, method: :delete, class: 'block p-2 hover:text-gray-600' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="mt-6 space-y-2">
|
||||
<li>
|
||||
<%= link_to root_path, class: 'block hover:bg-gray-100 -ml-2 p-2 text-sm font-semibold text-gray-900 flex items-center rounded' do %>
|
||||
<%= inline_svg_tag('icon-dashboard.svg', class: 'text-black stroke-current mr-2') %>
|
||||
Dashboard
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="mt-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs">Accounts</span>
|
||||
<%= link_to new_account_path, class: 'block hover:bg-gray-100 p-2 text-sm font-semibold text-gray-900 flex items-center rounded' do %>
|
||||
<%= inline_svg_tag('icon-add.svg', class: 'text-gray-500 fill-current') %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<main class="flex-grow py-5 pr-5">
|
||||
<%= yield %>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
52
app/views/layouts/auth.html.erb
Normal file
|
@ -0,0 +1,52 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="h-full bg-white">
|
||||
<head>
|
||||
<title>Maybe</title>
|
||||
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="Maybe">
|
||||
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
|
||||
|
||||
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
||||
<%= javascript_importmap_tags %>
|
||||
|
||||
<%= hotwire_livereload_tags if Rails.env.development? %>
|
||||
</head>
|
||||
|
||||
<body class="h-full">
|
||||
<div class="flex flex-col justify-center min-h-full px-6 py-12">
|
||||
|
||||
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<%= render "shared/logo" %>
|
||||
|
||||
<h2 class="mt-6 text-3xl font-semibold tracking-tight text-center font-display">
|
||||
<%= content_for?(:header_title) ? yield(:header_title).html_safe : "Your account" %>
|
||||
</h2>
|
||||
|
||||
<% if params[:controller] == "devise/sessions" && params[:action] == "new" %>
|
||||
<p class="mt-2 text-sm text-center text-gray-600">
|
||||
or <%= link_to "create an account", new_user_registration_path, class: 'font-medium text-candlelight-600 hover:text-candlelight-500' %>
|
||||
</p>
|
||||
<% elsif params[:controller] == "devise/registrations" && params[:action] == "new" %>
|
||||
<p class="mt-2 text-sm text-center text-gray-600">
|
||||
or <%= link_to "sign in to your account", new_user_session_path, class: 'font-medium text-candlelight-600 hover:text-candlelight-500' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-lg">
|
||||
<%= yield %>
|
||||
</div>
|
||||
|
||||
<div class="p-8 mt-2 text-center">
|
||||
<p class="mt-6 text-sm text-black"><a href="/privacy" class="text-black opacity-90">Privacy Policy</a> • <a href="/terms" class="text-black opacity-90">Terms of Service</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
13
app/views/layouts/mailer.html.erb
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<style>
|
||||
/* Email styles need to be inline */
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
1
app/views/layouts/mailer.text.erb
Normal file
|
@ -0,0 +1 @@
|
|||
<%= yield %>
|
1
app/views/pages/dashboard.html.erb
Normal file
|
@ -0,0 +1 @@
|
|||
<h1 class="text-3xl font-semibold font-display">Dashboard</h1>
|
1
app/views/password_mailer/password_reset.html.erb
Normal file
|
@ -0,0 +1 @@
|
|||
<%= link_to "Reset your password", edit_password_reset_url(token: params[:token]) %>
|
20
app/views/password_resets/edit.html.erb
Normal file
|
@ -0,0 +1,20 @@
|
|||
<%
|
||||
header_title "Reset password"
|
||||
%>
|
||||
|
||||
<%= form_with url: password_reset_path(token: params[:token]), html: {class: 'space-y-6'} do |form| %>
|
||||
|
||||
<div class="relative p-4 border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||
<%= form.label :password, class: 'block text-sm font-medium text-gray-700' %>
|
||||
<%= form.password_field :password, required: 'required', class: 'p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
||||
</div>
|
||||
|
||||
<div class="relative p-4 border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||
<%= form.label :password_confirmation, class: 'block text-sm font-medium text-gray-700' %>
|
||||
<%= form.password_field :password_confirmation, required: 'required', class: 'p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= form.submit "Reset Password", class: 'flex justify-center w-full px-4 py-3 text-sm font-medium text-white bg-black rounded-xl hover:bg-black focus:outline-none focus:ring-2 focus:ring-gray-200 shadow' %>
|
||||
</div>
|
||||
<% end %>
|
14
app/views/password_resets/new.html.erb
Normal file
|
@ -0,0 +1,14 @@
|
|||
<%
|
||||
header_title "Reset password"
|
||||
%>
|
||||
|
||||
<%= form_with url: password_reset_path, html: {class: 'space-y-6'} do |form| %>
|
||||
<div class="relative p-4 border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||
<%= form.label :email, class: 'block text-sm font-medium text-gray-700' %>
|
||||
<%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', class: 'p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= form.submit "Reset password", class: 'flex justify-center w-full px-4 py-3 text-sm font-medium text-white bg-black rounded-xl hover:bg-black focus:outline-none focus:ring-2 focus:ring-gray-200 shadow' %>
|
||||
</div>
|
||||
<% end %>
|
29
app/views/passwords/edit.html.erb
Normal file
|
@ -0,0 +1,29 @@
|
|||
<h1>Update Password</h1>
|
||||
|
||||
<%= form_with model: current_user, url: password_path do |form| %>
|
||||
<% if form.object.errors.any? %>
|
||||
<% form.object.errors.full_messages.each do |message| %>
|
||||
<div><%= message %></div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<div>
|
||||
<%= form.label :password_challenge, "Current Password" %>
|
||||
<%= form.password_field :password_challenge %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= form.label :password, "New Password" %>
|
||||
<%= form.password_field :password %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= form.label :password_confirmation %>
|
||||
<%= form.password_field :password_confirmation %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= form.submit 'Update Password' %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
22
app/views/pwa/manifest.json.erb
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "Maybe",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
{
|
||||
"src": "/icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"description": "Maybe.",
|
||||
"theme_color": "red",
|
||||
"background_color": "red"
|
||||
}
|
26
app/views/pwa/service-worker.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Add a service worker for processing Web Push notifications:
|
||||
//
|
||||
// self.addEventListener("push", async (event) => {
|
||||
// const { title, options } = await event.data.json()
|
||||
// event.waitUntil(self.registration.showNotification(title, options))
|
||||
// })
|
||||
//
|
||||
// self.addEventListener("notificationclick", function(event) {
|
||||
// event.notification.close()
|
||||
// event.waitUntil(
|
||||
// clients.matchAll({ type: "window" }).then((clientList) => {
|
||||
// for (let i = 0; i < clientList.length; i++) {
|
||||
// let client = clientList[i]
|
||||
// let clientPath = (new URL(client.url)).pathname
|
||||
//
|
||||
// if (clientPath == event.notification.data.path && "focus" in client) {
|
||||
// return client.focus()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (clients.openWindow) {
|
||||
// return clients.openWindow(event.notification.data.path)
|
||||
// }
|
||||
// })
|
||||
// )
|
||||
// })
|
34
app/views/registrations/new.html.erb
Normal file
|
@ -0,0 +1,34 @@
|
|||
<%
|
||||
header_title "Create an account"
|
||||
%>
|
||||
|
||||
<%= form_with model: @user, url: registration_path, html: {class: 'space-y-6'} do |form| %>
|
||||
<% if form.object.errors.any? %>
|
||||
<% form.object.errors.full_messages.each do |message| %>
|
||||
<div><%= message %></div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<div class="relative p-4 border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||
<%= form.label :email, class: 'block text-sm font-medium text-gray-700' %>
|
||||
<%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', class: 'p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
||||
</div>
|
||||
|
||||
<div class="relative p-4 border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||
<%= form.label :password, "Password", class: 'block text-sm font-medium text-gray-700' %>
|
||||
<%= form.password_field :password, autocomplete: "new-password", required: 'required', class: 'p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
||||
</div>
|
||||
|
||||
<div class="relative p-4 border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||
<%= form.label :password_confirmation, "Password confirmation", class: 'block text-sm font-medium text-gray-700' %>
|
||||
<%= form.password_field :password_confirmation, autocomplete: "new-password", required: 'required', class: 'p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= form.submit "Continue", class: 'flex justify-center w-full px-4 py-3 text-sm font-medium text-white bg-black rounded-xl hover:bg-black focus:outline-none focus:ring-2 focus:ring-gray-200 shadow' %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-sm text-gray-600">Already have an account? <%= link_to "Sign in", new_session_path, class: 'font-medium text-candlelight-600 hover:text-candlelight-500' %></p>
|
||||
</div>
|
28
app/views/sessions/new.html.erb
Normal file
|
@ -0,0 +1,28 @@
|
|||
<%
|
||||
header_title "Sign in to your account"
|
||||
%>
|
||||
|
||||
<div class="mt-0 text-center">
|
||||
<p class="text-sm text-gray-600">Don't have an account? <%= link_to "Create one", new_registration_path, class: 'font-medium text-candlelight-600 hover:text-candlelight-500' %></p>
|
||||
</div>
|
||||
|
||||
|
||||
<%= form_with url: session_path, html: {class: 'space-y-6'} do |form| %>
|
||||
<div class="relative p-4 border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||
<%= form.label :email, "Email address", class: 'block text-sm font-medium text-gray-700' %>
|
||||
<%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', class: 'p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
||||
</div>
|
||||
|
||||
<div class="relative p-4 border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||
<%= form.label :password, "Password", class: 'block text-sm font-medium text-gray-700' %>
|
||||
<%= form.password_field :password, required: 'required', class: 'p-0 mt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= form.submit "Log in", class: 'flex justify-center w-full px-4 py-3 text-sm font-medium text-white bg-black rounded-xl hover:bg-black focus:outline-none focus:ring-2 focus:ring-gray-200 shadow' %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-sm text-gray-600">Forgot your password? <%= link_to "Reset it", new_password_reset_path, class: 'font-medium text-candlelight-600 hover:text-candlelight-500' %></p>
|
||||
</div>
|
1
app/views/shared/_logo.html.erb
Normal file
|
@ -0,0 +1 @@
|
|||
<%= link_to image_tag("logomark.svg", class: 'w-auto h-12 mx-auto'), root_path, data: { turbo: false} %>
|