1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-02 12:05:19 +02:00

Initial commit

This commit is contained in:
Josh Pigford 2024-02-02 09:05:04 -06:00
commit 99de24ac70
147 changed files with 3519 additions and 0 deletions

0
app/assets/builds/.keep Normal file
View file

BIN
app/assets/fonts/.DS_Store vendored Normal file

Binary file not shown.

0
app/assets/images/.keep Normal file
View file

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View file

@ -0,0 +1 @@
/* Application styles */

View 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");
}
}

View file

@ -0,0 +1,4 @@
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end

View file

@ -0,0 +1,4 @@
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
end

View file

@ -0,0 +1,10 @@
class AccountsController < ApplicationController
def index
end
def new
end
def show
end
end

View 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

View file

View file

@ -0,0 +1,6 @@
class PagesController < ApplicationController
before_action :authenticate_user!
def dashboard
end
end

View 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

View 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

View 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

View 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

View file

@ -0,0 +1,2 @@
module AccountsHelper
end

View 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

View file

@ -0,0 +1,2 @@
module PagesHelper
end

View 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"

View 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 }

View 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());
}
}

View file

@ -0,0 +1,7 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
this.element.textContent = "Hello World!"
}
}

View 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)

View 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

View file

@ -0,0 +1,4 @@
class ApplicationMailer < ActionMailer::Base
default from: "from@example.com"
layout "mailer"
end

View 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
View file

@ -0,0 +1,3 @@
class Account < ApplicationRecord
belongs_to :family
end

View file

@ -0,0 +1,3 @@
class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
end

View file

3
app/models/current.rb Normal file
View file

@ -0,0 +1,3 @@
class Current < ActiveSupport::CurrentAttributes
attribute :user
end

4
app/models/family.rb Normal file
View 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
View 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

View 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>

View 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>

View 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>

View 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>

View 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> &bull; <a href="/terms" class="text-black opacity-90">Terms of Service</a></p>
</div>
</div>
</body>
</html>

View 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>

View file

@ -0,0 +1 @@
<%= yield %>

View file

@ -0,0 +1 @@
<h1 class="text-3xl font-semibold font-display">Dashboard</h1>

View file

@ -0,0 +1 @@
<%= link_to "Reset your password", edit_password_reset_url(token: params[:token]) %>

View 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 %>

View 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 %>

View 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 %>

View 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"
}

View 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)
// }
// })
// )
// })

View 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>

View 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>

View file

@ -0,0 +1 @@
<%= link_to image_tag("logomark.svg", class: 'w-auto h-12 mx-auto'), root_path, data: { turbo: false} %>