diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0dee090 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,549 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = tab +insert_final_newline = false +max_line_length = 120 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_visual_guides = none +ij_wrap_on_typing = false + +[*.css] +ij_css_align_closing_brace_with_properties = false +ij_css_blank_lines_around_nested_selector = 1 +ij_css_blank_lines_between_blocks = 1 +ij_css_block_comment_add_space = false +ij_css_brace_placement = end_of_line +ij_css_enforce_quotes_on_format = false +ij_css_hex_color_long_format = false +ij_css_hex_color_lower_case = false +ij_css_hex_color_short_format = false +ij_css_hex_color_upper_case = false +ij_css_keep_blank_lines_in_code = 2 +ij_css_keep_indents_on_empty_lines = false +ij_css_keep_single_line_blocks = false +ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_css_space_after_colon = true +ij_css_space_before_opening_brace = true +ij_css_use_double_quotes = true +ij_css_value_alignment = do_not_align + +[*.less] +ij_less_align_closing_brace_with_properties = false +ij_less_blank_lines_around_nested_selector = 1 +ij_less_blank_lines_between_blocks = 1 +ij_less_block_comment_add_space = false +ij_less_brace_placement = 0 +ij_less_enforce_quotes_on_format = false +ij_less_hex_color_long_format = false +ij_less_hex_color_lower_case = false +ij_less_hex_color_short_format = false +ij_less_hex_color_upper_case = false +ij_less_keep_blank_lines_in_code = 2 +ij_less_keep_indents_on_empty_lines = false +ij_less_keep_single_line_blocks = false +ij_less_line_comment_add_space = false +ij_less_line_comment_at_first_column = false +ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_less_space_after_colon = true +ij_less_space_before_opening_brace = true +ij_less_use_double_quotes = true +ij_less_value_alignment = 0 + +[*.sass] +ij_sass_align_closing_brace_with_properties = false +ij_sass_blank_lines_around_nested_selector = 1 +ij_sass_blank_lines_between_blocks = 1 +ij_sass_brace_placement = 0 +ij_sass_enforce_quotes_on_format = false +ij_sass_hex_color_long_format = false +ij_sass_hex_color_lower_case = false +ij_sass_hex_color_short_format = false +ij_sass_hex_color_upper_case = false +ij_sass_keep_blank_lines_in_code = 2 +ij_sass_keep_indents_on_empty_lines = false +ij_sass_keep_single_line_blocks = false +ij_sass_line_comment_add_space = false +ij_sass_line_comment_at_first_column = false +ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_sass_space_after_colon = true +ij_sass_space_before_opening_brace = true +ij_sass_use_double_quotes = true +ij_sass_value_alignment = 0 + +[*.scss] +ij_scss_align_closing_brace_with_properties = false +ij_scss_blank_lines_around_nested_selector = 1 +ij_scss_blank_lines_between_blocks = 1 +ij_scss_block_comment_add_space = false +ij_scss_brace_placement = 0 +ij_scss_enforce_quotes_on_format = false +ij_scss_hex_color_long_format = false +ij_scss_hex_color_lower_case = false +ij_scss_hex_color_short_format = false +ij_scss_hex_color_upper_case = false +ij_scss_keep_blank_lines_in_code = 2 +ij_scss_keep_indents_on_empty_lines = false +ij_scss_keep_single_line_blocks = false +ij_scss_line_comment_add_space = false +ij_scss_line_comment_at_first_column = false +ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_scss_space_after_colon = true +ij_scss_space_before_opening_brace = true +ij_scss_use_double_quotes = true +ij_scss_value_alignment = 0 + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.rng,*.tld,*.wadl,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_add_space = false +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal +ij_xml_use_custom_settings = false + +[{*.ats,*.cts,*.mts,*.ts}] +ij_typescript_align_imports = false +ij_typescript_align_multiline_array_initializer_expression = false +ij_typescript_align_multiline_binary_operation = false +ij_typescript_align_multiline_chained_methods = false +ij_typescript_align_multiline_extends_list = false +ij_typescript_align_multiline_for = true +ij_typescript_align_multiline_parameters = true +ij_typescript_align_multiline_parameters_in_calls = false +ij_typescript_align_multiline_ternary_operation = false +ij_typescript_align_object_properties = 0 +ij_typescript_align_union_types = false +ij_typescript_align_var_statements = 0 +ij_typescript_array_initializer_new_line_after_left_brace = false +ij_typescript_array_initializer_right_brace_on_new_line = false +ij_typescript_array_initializer_wrap = off +ij_typescript_assignment_wrap = off +ij_typescript_binary_operation_sign_on_next_line = false +ij_typescript_binary_operation_wrap = off +ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_typescript_blank_lines_after_imports = 1 +ij_typescript_blank_lines_around_class = 1 +ij_typescript_blank_lines_around_field = 0 +ij_typescript_blank_lines_around_field_in_interface = 0 +ij_typescript_blank_lines_around_function = 1 +ij_typescript_blank_lines_around_method = 1 +ij_typescript_blank_lines_around_method_in_interface = 1 +ij_typescript_block_brace_style = end_of_line +ij_typescript_block_comment_add_space = false +ij_typescript_block_comment_at_first_column = true +ij_typescript_call_parameters_new_line_after_left_paren = false +ij_typescript_call_parameters_right_paren_on_new_line = false +ij_typescript_call_parameters_wrap = off +ij_typescript_catch_on_new_line = false +ij_typescript_chained_call_dot_on_new_line = true +ij_typescript_class_brace_style = end_of_line +ij_typescript_comma_on_new_line = false +ij_typescript_do_while_brace_force = never +ij_typescript_else_on_new_line = false +ij_typescript_enforce_trailing_comma = keep +ij_typescript_enum_constants_wrap = on_every_item +ij_typescript_extends_keyword_wrap = off +ij_typescript_extends_list_wrap = off +ij_typescript_field_prefix = _ +ij_typescript_file_name_style = relaxed +ij_typescript_finally_on_new_line = false +ij_typescript_for_brace_force = never +ij_typescript_for_statement_new_line_after_left_paren = false +ij_typescript_for_statement_right_paren_on_new_line = false +ij_typescript_for_statement_wrap = off +ij_typescript_force_quote_style = false +ij_typescript_force_semicolon_style = false +ij_typescript_function_expression_brace_style = end_of_line +ij_typescript_if_brace_force = never +ij_typescript_import_merge_members = global +ij_typescript_import_prefer_absolute_path = global +ij_typescript_import_sort_members = true +ij_typescript_import_sort_module_name = false +ij_typescript_import_use_node_resolution = true +ij_typescript_imports_wrap = on_every_item +ij_typescript_indent_case_from_switch = true +ij_typescript_indent_chained_calls = true +ij_typescript_indent_package_children = 0 +ij_typescript_jsdoc_include_types = false +ij_typescript_jsx_attribute_value = braces +ij_typescript_keep_blank_lines_in_code = 2 +ij_typescript_keep_first_column_comment = true +ij_typescript_keep_indents_on_empty_lines = false +ij_typescript_keep_line_breaks = true +ij_typescript_keep_simple_blocks_in_one_line = false +ij_typescript_keep_simple_methods_in_one_line = false +ij_typescript_line_comment_add_space = true +ij_typescript_line_comment_at_first_column = false +ij_typescript_method_brace_style = end_of_line +ij_typescript_method_call_chain_wrap = off +ij_typescript_method_parameters_new_line_after_left_paren = false +ij_typescript_method_parameters_right_paren_on_new_line = false +ij_typescript_method_parameters_wrap = off +ij_typescript_object_literal_wrap = on_every_item +ij_typescript_parentheses_expression_new_line_after_left_paren = false +ij_typescript_parentheses_expression_right_paren_on_new_line = false +ij_typescript_place_assignment_sign_on_next_line = false +ij_typescript_prefer_as_type_cast = false +ij_typescript_prefer_explicit_types_function_expression_returns = false +ij_typescript_prefer_explicit_types_function_returns = false +ij_typescript_prefer_explicit_types_vars_fields = false +ij_typescript_prefer_parameters_wrap = false +ij_typescript_reformat_c_style_comments = false +ij_typescript_space_after_colon = true +ij_typescript_space_after_comma = true +ij_typescript_space_after_dots_in_rest_parameter = false +ij_typescript_space_after_generator_mult = true +ij_typescript_space_after_property_colon = true +ij_typescript_space_after_quest = true +ij_typescript_space_after_type_colon = true +ij_typescript_space_after_unary_not = false +ij_typescript_space_before_async_arrow_lparen = true +ij_typescript_space_before_catch_keyword = true +ij_typescript_space_before_catch_left_brace = true +ij_typescript_space_before_catch_parentheses = true +ij_typescript_space_before_class_lbrace = true +ij_typescript_space_before_class_left_brace = true +ij_typescript_space_before_colon = true +ij_typescript_space_before_comma = false +ij_typescript_space_before_do_left_brace = true +ij_typescript_space_before_else_keyword = true +ij_typescript_space_before_else_left_brace = true +ij_typescript_space_before_finally_keyword = true +ij_typescript_space_before_finally_left_brace = true +ij_typescript_space_before_for_left_brace = true +ij_typescript_space_before_for_parentheses = true +ij_typescript_space_before_for_semicolon = false +ij_typescript_space_before_function_left_parenth = true +ij_typescript_space_before_generator_mult = false +ij_typescript_space_before_if_left_brace = true +ij_typescript_space_before_if_parentheses = true +ij_typescript_space_before_method_call_parentheses = false +ij_typescript_space_before_method_left_brace = true +ij_typescript_space_before_method_parentheses = false +ij_typescript_space_before_property_colon = false +ij_typescript_space_before_quest = true +ij_typescript_space_before_switch_left_brace = true +ij_typescript_space_before_switch_parentheses = true +ij_typescript_space_before_try_left_brace = true +ij_typescript_space_before_type_colon = false +ij_typescript_space_before_unary_not = false +ij_typescript_space_before_while_keyword = true +ij_typescript_space_before_while_left_brace = true +ij_typescript_space_before_while_parentheses = true +ij_typescript_spaces_around_additive_operators = true +ij_typescript_spaces_around_arrow_function_operator = true +ij_typescript_spaces_around_assignment_operators = true +ij_typescript_spaces_around_bitwise_operators = true +ij_typescript_spaces_around_equality_operators = true +ij_typescript_spaces_around_logical_operators = true +ij_typescript_spaces_around_multiplicative_operators = true +ij_typescript_spaces_around_relational_operators = true +ij_typescript_spaces_around_shift_operators = true +ij_typescript_spaces_around_unary_operator = false +ij_typescript_spaces_within_array_initializer_brackets = false +ij_typescript_spaces_within_brackets = false +ij_typescript_spaces_within_catch_parentheses = false +ij_typescript_spaces_within_for_parentheses = false +ij_typescript_spaces_within_if_parentheses = false +ij_typescript_spaces_within_imports = false +ij_typescript_spaces_within_interpolation_expressions = false +ij_typescript_spaces_within_method_call_parentheses = false +ij_typescript_spaces_within_method_parentheses = false +ij_typescript_spaces_within_object_literal_braces = false +ij_typescript_spaces_within_object_type_braces = true +ij_typescript_spaces_within_parentheses = false +ij_typescript_spaces_within_switch_parentheses = false +ij_typescript_spaces_within_type_assertion = false +ij_typescript_spaces_within_union_types = true +ij_typescript_spaces_within_while_parentheses = false +ij_typescript_special_else_if_treatment = true +ij_typescript_ternary_operation_signs_on_next_line = false +ij_typescript_ternary_operation_wrap = off +ij_typescript_union_types_wrap = on_every_item +ij_typescript_use_chained_calls_group_indents = false +ij_typescript_use_double_quotes = true +ij_typescript_use_explicit_js_extension = auto +ij_typescript_use_path_mapping = always +ij_typescript_use_public_modifier = false +ij_typescript_use_semicolon_after_statement = true +ij_typescript_var_declaration_wrap = normal +ij_typescript_while_brace_force = never +ij_typescript_while_on_new_line = false +ij_typescript_wrap_comments = false + +[{*.cjs,*.js}] +ij_javascript_align_imports = false +ij_javascript_align_multiline_array_initializer_expression = false +ij_javascript_align_multiline_binary_operation = false +ij_javascript_align_multiline_chained_methods = false +ij_javascript_align_multiline_extends_list = false +ij_javascript_align_multiline_for = true +ij_javascript_align_multiline_parameters = true +ij_javascript_align_multiline_parameters_in_calls = false +ij_javascript_align_multiline_ternary_operation = false +ij_javascript_align_object_properties = 0 +ij_javascript_align_union_types = false +ij_javascript_align_var_statements = 0 +ij_javascript_array_initializer_new_line_after_left_brace = false +ij_javascript_array_initializer_right_brace_on_new_line = false +ij_javascript_array_initializer_wrap = off +ij_javascript_assignment_wrap = off +ij_javascript_binary_operation_sign_on_next_line = false +ij_javascript_binary_operation_wrap = off +ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_javascript_blank_lines_after_imports = 1 +ij_javascript_blank_lines_around_class = 1 +ij_javascript_blank_lines_around_field = 0 +ij_javascript_blank_lines_around_function = 1 +ij_javascript_blank_lines_around_method = 1 +ij_javascript_block_brace_style = end_of_line +ij_javascript_block_comment_add_space = false +ij_javascript_block_comment_at_first_column = true +ij_javascript_call_parameters_new_line_after_left_paren = false +ij_javascript_call_parameters_right_paren_on_new_line = false +ij_javascript_call_parameters_wrap = off +ij_javascript_catch_on_new_line = false +ij_javascript_chained_call_dot_on_new_line = true +ij_javascript_class_brace_style = end_of_line +ij_javascript_comma_on_new_line = false +ij_javascript_do_while_brace_force = never +ij_javascript_else_on_new_line = false +ij_javascript_enforce_trailing_comma = keep +ij_javascript_extends_keyword_wrap = off +ij_javascript_extends_list_wrap = off +ij_javascript_field_prefix = _ +ij_javascript_file_name_style = relaxed +ij_javascript_finally_on_new_line = false +ij_javascript_for_brace_force = never +ij_javascript_for_statement_new_line_after_left_paren = false +ij_javascript_for_statement_right_paren_on_new_line = false +ij_javascript_for_statement_wrap = off +ij_javascript_force_quote_style = false +ij_javascript_force_semicolon_style = false +ij_javascript_function_expression_brace_style = end_of_line +ij_javascript_if_brace_force = never +ij_javascript_import_merge_members = global +ij_javascript_import_prefer_absolute_path = global +ij_javascript_import_sort_members = true +ij_javascript_import_sort_module_name = false +ij_javascript_import_use_node_resolution = true +ij_javascript_imports_wrap = on_every_item +ij_javascript_indent_case_from_switch = true +ij_javascript_indent_chained_calls = true +ij_javascript_indent_package_children = 0 +ij_javascript_jsx_attribute_value = braces +ij_javascript_keep_blank_lines_in_code = 2 +ij_javascript_keep_first_column_comment = true +ij_javascript_keep_indents_on_empty_lines = false +ij_javascript_keep_line_breaks = true +ij_javascript_keep_simple_blocks_in_one_line = false +ij_javascript_keep_simple_methods_in_one_line = false +ij_javascript_line_comment_add_space = true +ij_javascript_line_comment_at_first_column = false +ij_javascript_method_brace_style = end_of_line +ij_javascript_method_call_chain_wrap = off +ij_javascript_method_parameters_new_line_after_left_paren = false +ij_javascript_method_parameters_right_paren_on_new_line = false +ij_javascript_method_parameters_wrap = off +ij_javascript_object_literal_wrap = on_every_item +ij_javascript_parentheses_expression_new_line_after_left_paren = false +ij_javascript_parentheses_expression_right_paren_on_new_line = false +ij_javascript_place_assignment_sign_on_next_line = false +ij_javascript_prefer_as_type_cast = false +ij_javascript_prefer_explicit_types_function_expression_returns = false +ij_javascript_prefer_explicit_types_function_returns = false +ij_javascript_prefer_explicit_types_vars_fields = false +ij_javascript_prefer_parameters_wrap = false +ij_javascript_reformat_c_style_comments = false +ij_javascript_space_after_colon = true +ij_javascript_space_after_comma = true +ij_javascript_space_after_dots_in_rest_parameter = false +ij_javascript_space_after_generator_mult = true +ij_javascript_space_after_property_colon = true +ij_javascript_space_after_quest = true +ij_javascript_space_after_type_colon = true +ij_javascript_space_after_unary_not = false +ij_javascript_space_before_async_arrow_lparen = true +ij_javascript_space_before_catch_keyword = true +ij_javascript_space_before_catch_left_brace = true +ij_javascript_space_before_catch_parentheses = true +ij_javascript_space_before_class_lbrace = true +ij_javascript_space_before_class_left_brace = true +ij_javascript_space_before_colon = true +ij_javascript_space_before_comma = false +ij_javascript_space_before_do_left_brace = true +ij_javascript_space_before_else_keyword = true +ij_javascript_space_before_else_left_brace = true +ij_javascript_space_before_finally_keyword = true +ij_javascript_space_before_finally_left_brace = true +ij_javascript_space_before_for_left_brace = true +ij_javascript_space_before_for_parentheses = true +ij_javascript_space_before_for_semicolon = false +ij_javascript_space_before_function_left_parenth = true +ij_javascript_space_before_generator_mult = false +ij_javascript_space_before_if_left_brace = true +ij_javascript_space_before_if_parentheses = true +ij_javascript_space_before_method_call_parentheses = false +ij_javascript_space_before_method_left_brace = true +ij_javascript_space_before_method_parentheses = false +ij_javascript_space_before_property_colon = false +ij_javascript_space_before_quest = true +ij_javascript_space_before_switch_left_brace = true +ij_javascript_space_before_switch_parentheses = true +ij_javascript_space_before_try_left_brace = true +ij_javascript_space_before_type_colon = false +ij_javascript_space_before_unary_not = false +ij_javascript_space_before_while_keyword = true +ij_javascript_space_before_while_left_brace = true +ij_javascript_space_before_while_parentheses = true +ij_javascript_spaces_around_additive_operators = true +ij_javascript_spaces_around_arrow_function_operator = true +ij_javascript_spaces_around_assignment_operators = true +ij_javascript_spaces_around_bitwise_operators = true +ij_javascript_spaces_around_equality_operators = true +ij_javascript_spaces_around_logical_operators = true +ij_javascript_spaces_around_multiplicative_operators = true +ij_javascript_spaces_around_relational_operators = true +ij_javascript_spaces_around_shift_operators = true +ij_javascript_spaces_around_unary_operator = false +ij_javascript_spaces_within_array_initializer_brackets = false +ij_javascript_spaces_within_brackets = false +ij_javascript_spaces_within_catch_parentheses = false +ij_javascript_spaces_within_for_parentheses = false +ij_javascript_spaces_within_if_parentheses = false +ij_javascript_spaces_within_imports = false +ij_javascript_spaces_within_interpolation_expressions = false +ij_javascript_spaces_within_method_call_parentheses = false +ij_javascript_spaces_within_method_parentheses = false +ij_javascript_spaces_within_object_literal_braces = false +ij_javascript_spaces_within_object_type_braces = true +ij_javascript_spaces_within_parentheses = false +ij_javascript_spaces_within_switch_parentheses = false +ij_javascript_spaces_within_type_assertion = false +ij_javascript_spaces_within_union_types = true +ij_javascript_spaces_within_while_parentheses = false +ij_javascript_special_else_if_treatment = true +ij_javascript_ternary_operation_signs_on_next_line = false +ij_javascript_ternary_operation_wrap = off +ij_javascript_union_types_wrap = on_every_item +ij_javascript_use_chained_calls_group_indents = false +ij_javascript_use_double_quotes = true +ij_javascript_use_explicit_js_extension = auto +ij_javascript_use_path_mapping = always +ij_javascript_use_public_modifier = false +ij_javascript_use_semicolon_after_statement = true +ij_javascript_var_declaration_wrap = normal +ij_javascript_while_brace_force = never +ij_javascript_while_on_new_line = false +ij_javascript_wrap_comments = false + +[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}] +ij_json_array_wrapping = split_into_lines +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_keep_trailing_comma = false +ij_json_object_wrapping = split_into_lines +ij_json_property_alignment = do_not_align +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = false +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_add_space = false +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span,pre,textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal + +[{*.markdown,*.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_format_tables = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + +[{*.properties,spring.handlers,spring.schemas}] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] +ij_toml_keep_indents_on_empty_lines = false + +[{*.yaml,*.yml}] +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/src/lib/server/api/iam/iam.controller.ts b/src/lib/server/api/iam/iam.controller.ts index 0aa1708..5698a01 100644 --- a/src/lib/server/api/iam/iam.controller.ts +++ b/src/lib/server/api/iam/iam.controller.ts @@ -1,42 +1,40 @@ import { inject, injectable } from '@needle-di/core'; import { zValidator } from '@hono/zod-validator'; +import { openApi } from 'hono-zod-openapi'; import { createLoginRequestDto } from '../iam/login-requests/dtos/create-login-request.dto'; import { LoginRequestsService } from '../iam/login-requests/login-requests.service'; import { verifyLoginRequestDto } from '../iam/login-requests/dtos/verify-login-request.dto'; import { SessionsService } from '../iam/sessions/sessions.service'; import { authState } from '../common/middleware/auth.middleware'; import { Controller } from '../common/factories/controllers.factory'; +import { loginRequestDto } from './login-requests/dtos/login-request.dto'; +import { signInEmail } from './login-requests/routes/login.routes'; @injectable() export class IamController extends Controller { constructor( private loginRequestsService = inject(LoginRequestsService), - private sessionsService = inject(SessionsService) + private sessionsService = inject(SessionsService), ) { super(); } routes() { return this.controller - .post( - '/login/request', - authState('none'), - zValidator('json', createLoginRequestDto), - async (c) => { - await this.loginRequestsService.sendVerificationCode(c.req.valid('json')); - return c.json({ message: 'welcome' }); - } - ) - .post( - '/login/verify', - authState('none'), - zValidator('json', verifyLoginRequestDto), - async (c) => { - const session = await this.loginRequestsService.verify(c.req.valid('json')); - await this.sessionsService.setSessionCookie(session); - return c.json({ message: 'welcome' }); - } - ) + .post('/login', openApi(signInEmail), authState('none'), zValidator('json', loginRequestDto), async (c) => { + const session = await this.loginRequestsService.login(c.req.valid('json')); + await this.sessionsService.setSessionCookie(session); + return c.json({ message: 'welcome' }); + }) + .post('/login/request', authState('none'), zValidator('json', createLoginRequestDto), async (c) => { + await this.loginRequestsService.sendVerificationCode(c.req.valid('json')); + return c.json({ message: 'welcome' }); + }) + .post('/login/verify', authState('none'), zValidator('json', verifyLoginRequestDto), async (c) => { + const session = await this.loginRequestsService.verify(c.req.valid('json')); + await this.sessionsService.setSessionCookie(session); + return c.json({ message: 'welcome' }); + }) .post('/logout', async (c) => { await this.sessionsService.invalidateSession(''); this.sessionsService.deleteSessionCookie(); diff --git a/src/lib/server/api/iam/login-requests/dtos/login-request.dto.ts b/src/lib/server/api/iam/login-requests/dtos/login-request.dto.ts new file mode 100644 index 0000000..7089a4b --- /dev/null +++ b/src/lib/server/api/iam/login-requests/dtos/login-request.dto.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; + +export const loginRequestDto = z.object({ + email: z.string().email(), + password: z.string({ required_error: 'Password is required' }), +}); + +export type LoginRequestDto = z.infer; diff --git a/src/lib/server/api/iam/login-requests/login-requests.service.ts b/src/lib/server/api/iam/login-requests/login-requests.service.ts index 48e77e8..3e41beb 100644 --- a/src/lib/server/api/iam/login-requests/login-requests.service.ts +++ b/src/lib/server/api/iam/login-requests/login-requests.service.ts @@ -2,7 +2,7 @@ import { inject, injectable } from '@needle-di/core'; import { LoginRequestsRepository } from './login-requests.repository'; import { MailerService } from '../../mail/mailer.service'; import { LoginVerificationEmail } from '../../mail/templates/login-verification.template'; -import { BadRequest } from '../../common/utils/exceptions'; +import { BadRequest, NotFound } from '../../common/utils/exceptions'; import { WelcomeEmail } from '../../mail/templates/welcome.template'; import { SessionsService } from '../sessions/sessions.service'; import type { VerifyLoginRequestDto } from './dtos/verify-login-request.dto'; @@ -10,18 +10,43 @@ import type { CreateLoginRequestDto } from './dtos/create-login-request.dto'; import { UsersService } from '../../users/users.service'; import { UsersRepository } from '../../users/users.repository'; import { VerificationCodesService } from '../../common/services/verification-codes.service'; +import type { LoginRequestDto } from './dtos/login-request.dto'; +import { CredentialsRepository } from '../../users/credentials.repository'; @injectable() export class LoginRequestsService { constructor( + private credentialsRepository = inject(CredentialsRepository), private loginRequestsRepository = inject(LoginRequestsRepository), private usersRepository = inject(UsersRepository), private verificationCodesService = inject(VerificationCodesService), private usersService = inject(UsersService), private sessionsService = inject(SessionsService), - private mailer = inject(MailerService) + private mailer = inject(MailerService), ) {} + async login({ email, password }: LoginRequestDto) { + const existingUser = await this.usersRepository.findOneByEmail(email); + + if (!existingUser) { + throw NotFound('User not found'); + } + + const credential = await this.credentialsRepository.findPasswordCredentialsByUserId(existingUser.id); + + if (!credential) { + throw BadRequest('Invalid credentials'); + } + + if (!(await this.tokensService.verifyHashedToken(credential.secret_data, data.password))) { + throw BadRequest('Invalid credentials'); + } + + const totpCredentials = await this.credentialsRepository.findTOTPCredentialsByUserId(existingUser.id); + + return this.authExistingUser({ userId: existingUser.id }); + } + async verify({ email, code }: VerifyLoginRequestDto) { // find the hashed verification code for the email const loginRequest = await this.loginRequestsRepository.get(email); @@ -32,7 +57,7 @@ export class LoginRequestsService { // verify the code const isValid = await this.verificationCodesService.verify({ verificationCode: code, - hashedVerificationCode: loginRequest.hashedCode + hashedVerificationCode: loginRequest.hashedCode, }); // if the code is invalid, throw an error @@ -45,9 +70,7 @@ export class LoginRequestsService { const existingUser = await this.usersRepository.findOneByEmail(email); // if the user exists, log them in, otherwise create a new user and log them in - return existingUser - ? this.authExistingUser({ userId: existingUser.id }) - : this.authNewUser({ email }); + return existingUser ? this.authExistingUser({ userId: existingUser.id }) : this.authNewUser({ email }); } async sendVerificationCode({ email }: CreateLoginRequestDto) { @@ -55,19 +78,18 @@ export class LoginRequestsService { await this.loginRequestsRepository.delete(email); // generate a new verification code and hash - const { verificationCode, hashedVerificationCode } = - await this.verificationCodesService.generateCodeWithHash(); + const { verificationCode, hashedVerificationCode } = await this.verificationCodesService.generateCodeWithHash(); // create a new login request await this.loginRequestsRepository.set({ email, - hashedCode: hashedVerificationCode + hashedCode: hashedVerificationCode, }); // send the verification email await this.mailer.send({ to: email, - template: new LoginVerificationEmail(verificationCode) + template: new LoginVerificationEmail(verificationCode), }); } @@ -78,7 +100,7 @@ export class LoginRequestsService { // send the welcome email await this.mailer.send({ to: email, - template: new WelcomeEmail() + template: new WelcomeEmail(), }); // create a new session diff --git a/src/lib/server/api/iam/login-requests/routes/login.routes.ts b/src/lib/server/api/iam/login-requests/routes/login.routes.ts new file mode 100644 index 0000000..f5cbe11 --- /dev/null +++ b/src/lib/server/api/iam/login-requests/routes/login.routes.ts @@ -0,0 +1,21 @@ +import { StatusCodes } from '$lib/utils/status-codes'; +import { defineOpenApiOperation } from 'hono-zod-openapi'; +import { createErrorSchema } from 'stoker/openapi/schemas'; +import { loginRequestDto } from '../dtos/login-request.dto'; + +export const signInEmail = defineOpenApiOperation({ + tags: ['Login'], + summary: 'Sign in with email', + description: 'Sign in with email', + responses: { + [StatusCodes.OK]: { + description: 'Sign in with username', + schema: loginRequestDto, + }, + [StatusCodes.UNPROCESSABLE_ENTITY]: { + description: 'The validation error(s)', + schema: createErrorSchema(loginRequestDto), + mediaType: 'application/json', + }, + }, +}); diff --git a/src/lib/server/api/users/credentials.repository.ts b/src/lib/server/api/users/credentials.repository.ts new file mode 100644 index 0000000..9548395 --- /dev/null +++ b/src/lib/server/api/users/credentials.repository.ts @@ -0,0 +1,73 @@ +import { CredentialsType, credentials_table } from './tables/credentials.table'; +import { injectable } from '@needle-di/core'; +import { and, eq, type InferSelectModel } from 'drizzle-orm'; +import { takeFirstOrThrow } from '../common/utils/drizzle'; +import { DrizzleRepository } from '../common/factories/drizzle-repository.factory'; + +/* -------------------------------------------------------------------------- */ +/* Types */ +/* -------------------------------------------------------------------------- */ +type Create = Pick, 'user_id' | 'type' | 'secret_data'>; +type Update = Partial; + +/* -------------------------------------------------------------------------- */ +/* Repository */ +/* -------------------------------------------------------------------------- */ +@injectable() +export class CredentialsRepository extends DrizzleRepository { + async findOneByUserId(userId: string, db = this.drizzle.db) { + return db.query.credentials_table.findFirst({ + where: eq(credentials_table.user_id, userId), + }); + } + + async findOneByUserIdAndType(userId: string, type: CredentialsType, db = this.drizzle.db) { + return db.query.credentials_table.findFirst({ + where: and(eq(credentials_table.user_id, userId), eq(credentials_table.type, type)), + }); + } + + async findPasswordCredentialsByUserId(userId: string, db = this.drizzle.db) { + return db.query.credentials_table.findFirst({ + where: and(eq(credentials_table.user_id, userId), eq(credentials_table.type, CredentialsType.PASSWORD)), + }); + } + + async findTOTPCredentialsByUserId(userId: string, db = this.drizzle.db) { + return db.query.credentials_table.findFirst({ + where: and(eq(credentials_table.user_id, userId), eq(credentials_table.type, CredentialsType.TOTP)), + }); + } + + async findOneById(id: string, db = this.drizzle.db) { + return db.query.credentials_table.findFirst({ + where: eq(credentials_table.id, id), + }); + } + + async findOneByIdOrThrow(id: string, db = this.drizzle.db) { + const credentials = await this.findOneById(id, db); + if (!credentials) throw Error('Credentials not found'); + return credentials; + } + + async create(data: Create, db = this.drizzle.db) { + return db.insert(credentials_table).values(data).returning().then(takeFirstOrThrow); + } + + async update(id: string, data: Update, db = this.drizzle.db) { + return db.update(credentials_table).set(data).where(eq(credentials_table.id, id)).returning().then(takeFirstOrThrow); + } + + async delete(id: string, db = this.drizzle.db) { + return db.delete(credentials_table).where(eq(credentials_table.id, id)); + } + + async deleteByUserId(userId: string, db = this.drizzle.db) { + return db.delete(credentials_table).where(eq(credentials_table.user_id, userId)); + } + + async deleteByUserIdAndType(userId: string, type: CredentialsType, db = this.drizzle.db) { + return db.delete(credentials_table).where(and(eq(credentials_table.user_id, userId), eq(credentials_table.type, type))); + } +} diff --git a/src/lib/tanstack-query/iam.ts b/src/lib/tanstack-query/iam.ts index d93eb1d..3a4082b 100644 --- a/src/lib/tanstack-query/iam.ts +++ b/src/lib/tanstack-query/iam.ts @@ -6,6 +6,7 @@ import { TanstackQueryModule } from './query-module'; /* -------------------------------------------------------------------------- */ /* Types */ /* -------------------------------------------------------------------------- */ +type RequestUsernamePasswordLogin = Api['iam']['login']['$post']; type RequestLogin = Api['iam']['login']['request']['$post']; type VerifyLogin = Api['iam']['login']['verify']['$post']; type Logout = Api['iam']['logout']['$post']; @@ -19,6 +20,12 @@ export class IamModule extends TanstackQueryModule<'iam'> { mutationFn: async () => await this.api.iam.logout.$post().then(parseApiResponse) }; } + requestUsernamePasswordLogin(): ApiMutation { + return { + mutationFn: async (data: InferRequestType) => + await this.api.iam.login.$post(data).then(parseApiResponse) + } + } requestLogin(): ApiMutation { return { mutationFn: async (data: InferRequestType) => diff --git a/src/routes/(app)/login/(components)/login-form.svelte b/src/routes/(app)/login/(components)/login-form.svelte index c24c2b9..8139a85 100644 --- a/src/routes/(app)/login/(components)/login-form.svelte +++ b/src/routes/(app)/login/(components)/login-form.svelte @@ -2,9 +2,14 @@ import { z } from 'zod'; export const loginSchema = z.object({ - email: z.string().email() + email: z.string().email(), }); + export const loginPasswordSchema = z.object({ + email: z.string().email(), + password: z.string({ required_error: 'Password is required' }), + }) + export const verifySchema = z.object({ email: z.string().email(), code: z.string().length(6) @@ -27,13 +32,22 @@ const RESEND_VERIFICATION_CODE_COOLDOWN = 60; let queryClient = useQueryClient(); - let step = $state<'request' | 'verify'>('request'); + let step = $state<'login' | 'request' | 'verify'>('request'); let countdownTimer = $state(RESEND_VERIFICATION_CODE_COOLDOWN); let resendVerificationCodeOnCooldown = $derived( countdownTimer != RESEND_VERIFICATION_CODE_COOLDOWN ); /* ----------------------------------- Api ---------------------------------- */ + const requestUsernamePasswordLoginMutation = createMutation({ + ...queryHandler().iam.requestUsernamePasswordLogin(), + onSuccess(_data, variables, _context) { + step = 'verify'; + $verifyForm.email = variables.json.email; + $verifyForm. + } + }) + const requestMutation = createMutation({ ...queryHandler().iam.requestLogin(), onSuccess(_data, variables, _context) { @@ -58,6 +72,16 @@ }); /* ------------------------------- Login Form ------------------------------- */ + const sf_login_username_password = superForm(defaults(zod(loginPasswordSchema)), { + resetForm: false, + SPA: true, + validators: zod(loginPasswordSchema), + async onUpdated(event) { + if (!event.form.valid) return; + await $requestUsernamePasswordLogin.mutateAsync({ json: event.form.data }); + } + }) + const sf_login = superForm(defaults(zod(loginSchema)), { resetForm: false, SPA: true, @@ -78,6 +102,8 @@ $verifyForm.email = event.form.data.email; } }); + const { form: loginPasswordForm, enhance: loginPasswordEnhance, errors: loginPasswordErrors } = sf_login_username_password; + const { form: loginForm, enhance: loginEnhance, errors: requestErrors } = sf_login; /* ------------------------------- Verify Form ------------------------------ */ @@ -118,14 +144,51 @@ } +{#if step === 'login'} + {@render loginCard()} +{/if} {#if step === 'request'} - {@render requetsCard()} + {@render requestsCard()} {/if} {#if step === 'verify'} {@render verifyCard()} {/if} -{#snippet requetsCard()} +{#snippet loginCard()} + + + Login + Enter your email below to login to your account + + +
+ + + {#snippet children({ props })} + Email + + {/snippet} + + + + + + + {#snippet children({ props })} + Password + + {/snippet} + + + + + +
+
+
+{/snippet} + +{#snippet requestsCard()} Login