Merge branch 'main' into dev

This commit is contained in:
Leandro Antônio Farias Machado 2023-03-09 22:42:12 -03:00
commit efb385161d
117 changed files with 13926 additions and 2 deletions

2
.gitignore vendored
View File

@ -1,2 +0,0 @@
*.log
*.db

11
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
*.exe
*.exe~
*.dll
*.so
*.dylib
main
*.test
*.out
go.work
*.log
*.db

694
frontend/.editorconfig Normal file
View File

@ -0,0 +1,694 @@
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = false
max_line_length = 100
tab_width = 2
ij_continuation_indent_size = 2
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = true
ij_smart_tabs = false
ij_visual_guides = 100
ij_wrap_on_typing = false
[*.css]
ij_continuation_indent_size = 2
ij_visual_guides = none
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_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
[*.ejs]
ij_visual_guides = none
[*.feature]
tab_width = 4
ij_visual_guides = none
ij_gherkin_keep_indents_on_empty_lines = false
[*.haml]
tab_width = 4
ij_visual_guides = none
ij_haml_keep_indents_on_empty_lines = false
[*.less]
tab_width = 4
ij_visual_guides = none
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_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_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]
tab_width = 4
ij_visual_guides = none
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_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]
tab_width = 4
ij_visual_guides = none
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_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_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
[*.styl]
tab_width = 4
ij_visual_guides = none
ij_stylus_align_closing_brace_with_properties = false
ij_stylus_blank_lines_around_nested_selector = 1
ij_stylus_blank_lines_between_blocks = 1
ij_stylus_brace_placement = 0
ij_stylus_enforce_quotes_on_format = false
ij_stylus_hex_color_long_format = false
ij_stylus_hex_color_lower_case = false
ij_stylus_hex_color_short_format = false
ij_stylus_hex_color_upper_case = false
ij_stylus_keep_blank_lines_in_code = 2
ij_stylus_keep_indents_on_empty_lines = false
ij_stylus_keep_single_line_blocks = false
ij_stylus_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_stylus_space_after_colon = true
ij_stylus_space_before_opening_brace = true
ij_stylus_use_double_quotes = true
ij_stylus_value_alignment = 0
[*.vue]
ij_continuation_indent_size = 4
ij_visual_guides = none
ij_vue_indent_children_of_top_level = template
ij_vue_interpolation_new_line_after_start_delimiter = true
ij_vue_interpolation_new_line_before_end_delimiter = true
ij_vue_interpolation_wrap = off
ij_vue_keep_indents_on_empty_lines = false
ij_vue_spaces_within_interpolation_expressions = true
ij_vue_uniform_indent = true
[.editorconfig]
ij_visual_guides = none
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,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
indent_size = 4
tab_width = 4
ij_visual_guides = none
ij_xml_align_attributes = true
ij_xml_align_text = false
ij_xml_attribute_wrap = normal
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
[{*.ats,*.ts,*.tsx}]
ij_continuation_indent_size = 2
ij_visual_guides = none
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 = true
ij_typescript_align_multiline_extends_list = false
ij_typescript_align_multiline_for = false
ij_typescript_align_multiline_parameters = false
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 = true
ij_typescript_array_initializer_right_brace_on_new_line = true
ij_typescript_array_initializer_wrap = on_every_item
ij_typescript_assignment_wrap = on_every_item
ij_typescript_binary_operation_sign_on_next_line = true
ij_typescript_binary_operation_wrap = on_every_item
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_call_parameters_new_line_after_left_paren = false
ij_typescript_call_parameters_right_paren_on_new_line = false
ij_typescript_call_parameters_wrap = on_every_item
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 = always
ij_typescript_else_on_new_line = false
ij_typescript_enforce_trailing_comma = remove
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 = always
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 = true
ij_typescript_force_semicolon_style = true
ij_typescript_function_expression_brace_style = end_of_line
ij_typescript_if_brace_force = always
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 = 1
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 = true
ij_typescript_keep_simple_methods_in_one_line = true
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 = on_every_item
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 = on_every_item
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 = true
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 = true
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 = true
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 = true
ij_typescript_ternary_operation_wrap = on_every_item
ij_typescript_union_types_wrap = on_every_item
ij_typescript_use_chained_calls_group_indents = false
ij_typescript_use_double_quotes = false
ij_typescript_use_explicit_js_extension = global
ij_typescript_use_path_mapping = always
ij_typescript_use_public_modifier = false
ij_typescript_use_semicolon_after_statement = true
ij_typescript_var_declaration_wrap = on_every_item
ij_typescript_while_brace_force = always
ij_typescript_while_on_new_line = false
ij_typescript_wrap_comments = false
[{*.bash,*.sh,*.zsh}]
ij_visual_guides = none
ij_shell_binary_ops_start_line = false
ij_shell_keep_column_alignment_padding = false
ij_shell_minify_program = false
ij_shell_redirect_followed_by_space = false
ij_shell_switch_cases_indented = false
[{*.cjs,*.js,*.mjs}]
ij_continuation_indent_size = 2
ij_visual_guides = none
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 = true
ij_javascript_align_multiline_extends_list = false
ij_javascript_align_multiline_for = false
ij_javascript_align_multiline_parameters = false
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 = true
ij_javascript_array_initializer_right_brace_on_new_line = true
ij_javascript_array_initializer_wrap = on_every_item
ij_javascript_assignment_wrap = on_every_item
ij_javascript_binary_operation_sign_on_next_line = true
ij_javascript_binary_operation_wrap = on_every_item
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_call_parameters_new_line_after_left_paren = false
ij_javascript_call_parameters_right_paren_on_new_line = false
ij_javascript_call_parameters_wrap = on_every_item
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 = always
ij_javascript_else_on_new_line = false
ij_javascript_enforce_trailing_comma = remove
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 = always
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 = true
ij_javascript_force_semicolon_style = true
ij_javascript_function_expression_brace_style = end_of_line
ij_javascript_if_brace_force = always
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 = 1
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 = true
ij_javascript_keep_simple_methods_in_one_line = true
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 = on_every_item
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 = on_every_item
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 = true
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 = true
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 = true
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 = true
ij_javascript_ternary_operation_wrap = on_every_item
ij_javascript_union_types_wrap = on_every_item
ij_javascript_use_chained_calls_group_indents = false
ij_javascript_use_double_quotes = false
ij_javascript_use_explicit_js_extension = global
ij_javascript_use_path_mapping = always
ij_javascript_use_public_modifier = false
ij_javascript_use_semicolon_after_statement = true
ij_javascript_var_declaration_wrap = on_every_item
ij_javascript_while_brace_force = always
ij_javascript_while_on_new_line = false
ij_javascript_wrap_comments = false
[{*.cjsx,*.coffee}]
ij_continuation_indent_size = 2
ij_visual_guides = none
ij_coffeescript_align_function_body = false
ij_coffeescript_align_imports = false
ij_coffeescript_align_multiline_array_initializer_expression = true
ij_coffeescript_align_multiline_parameters = true
ij_coffeescript_align_multiline_parameters_in_calls = false
ij_coffeescript_align_object_properties = 0
ij_coffeescript_align_union_types = false
ij_coffeescript_align_var_statements = 0
ij_coffeescript_array_initializer_new_line_after_left_brace = false
ij_coffeescript_array_initializer_right_brace_on_new_line = false
ij_coffeescript_array_initializer_wrap = normal
ij_coffeescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/**
ij_coffeescript_blank_lines_around_function = 1
ij_coffeescript_call_parameters_new_line_after_left_paren = false
ij_coffeescript_call_parameters_right_paren_on_new_line = false
ij_coffeescript_call_parameters_wrap = normal
ij_coffeescript_chained_call_dot_on_new_line = true
ij_coffeescript_comma_on_new_line = false
ij_coffeescript_enforce_trailing_comma = keep
ij_coffeescript_field_prefix = _
ij_coffeescript_file_name_style = relaxed
ij_coffeescript_force_quote_style = false
ij_coffeescript_force_semicolon_style = false
ij_coffeescript_function_expression_brace_style = end_of_line
ij_coffeescript_import_merge_members = global
ij_coffeescript_import_prefer_absolute_path = global
ij_coffeescript_import_sort_members = true
ij_coffeescript_import_sort_module_name = false
ij_coffeescript_import_use_node_resolution = true
ij_coffeescript_imports_wrap = on_every_item
ij_coffeescript_indent_chained_calls = true
ij_coffeescript_indent_package_children = 0
ij_coffeescript_jsx_attribute_value = braces
ij_coffeescript_keep_blank_lines_in_code = 2
ij_coffeescript_keep_first_column_comment = true
ij_coffeescript_keep_indents_on_empty_lines = false
ij_coffeescript_keep_line_breaks = true
ij_coffeescript_keep_simple_methods_in_one_line = false
ij_coffeescript_method_parameters_new_line_after_left_paren = false
ij_coffeescript_method_parameters_right_paren_on_new_line = false
ij_coffeescript_method_parameters_wrap = off
ij_coffeescript_object_literal_wrap = on_every_item
ij_coffeescript_prefer_as_type_cast = false
ij_coffeescript_prefer_explicit_types_function_expression_returns = false
ij_coffeescript_prefer_explicit_types_function_returns = false
ij_coffeescript_prefer_explicit_types_vars_fields = false
ij_coffeescript_reformat_c_style_comments = false
ij_coffeescript_space_after_comma = true
ij_coffeescript_space_after_dots_in_rest_parameter = false
ij_coffeescript_space_after_generator_mult = true
ij_coffeescript_space_after_property_colon = true
ij_coffeescript_space_after_type_colon = true
ij_coffeescript_space_after_unary_not = false
ij_coffeescript_space_before_async_arrow_lparen = true
ij_coffeescript_space_before_class_lbrace = true
ij_coffeescript_space_before_comma = false
ij_coffeescript_space_before_function_left_parenth = true
ij_coffeescript_space_before_generator_mult = false
ij_coffeescript_space_before_property_colon = false
ij_coffeescript_space_before_type_colon = false
ij_coffeescript_space_before_unary_not = false
ij_coffeescript_spaces_around_additive_operators = true
ij_coffeescript_spaces_around_arrow_function_operator = true
ij_coffeescript_spaces_around_assignment_operators = true
ij_coffeescript_spaces_around_bitwise_operators = true
ij_coffeescript_spaces_around_equality_operators = true
ij_coffeescript_spaces_around_logical_operators = true
ij_coffeescript_spaces_around_multiplicative_operators = true
ij_coffeescript_spaces_around_relational_operators = true
ij_coffeescript_spaces_around_shift_operators = true
ij_coffeescript_spaces_around_unary_operator = false
ij_coffeescript_spaces_within_array_initializer_braces = false
ij_coffeescript_spaces_within_array_initializer_brackets = false
ij_coffeescript_spaces_within_imports = false
ij_coffeescript_spaces_within_index_brackets = false
ij_coffeescript_spaces_within_interpolation_expressions = false
ij_coffeescript_spaces_within_method_call_parentheses = false
ij_coffeescript_spaces_within_method_parentheses = false
ij_coffeescript_spaces_within_object_braces = false
ij_coffeescript_spaces_within_object_literal_braces = false
ij_coffeescript_spaces_within_object_type_braces = true
ij_coffeescript_spaces_within_range_brackets = false
ij_coffeescript_spaces_within_type_assertion = false
ij_coffeescript_spaces_within_union_types = true
ij_coffeescript_union_types_wrap = on_every_item
ij_coffeescript_use_chained_calls_group_indents = false
ij_coffeescript_use_double_quotes = true
ij_coffeescript_use_explicit_js_extension = global
ij_coffeescript_use_path_mapping = always
ij_coffeescript_use_public_modifier = false
ij_coffeescript_use_semicolon_after_statement = false
ij_coffeescript_var_declaration_wrap = normal
[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}]
tab_width = 4
ij_visual_guides = none
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
ij_json_keep_line_breaks = true
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}]
indent_size = 2
tab_width = 2
ij_continuation_indent_size = 2
ij_visual_guides = none
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 = on_every_item
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 = 1
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 = when multiline
ij_html_new_line_before_first_attribute = when multiline
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 = true
ij_html_text_wrap = off
ij_html_uniform_ident = false
[{*.jade,*.pug}]
indent_size = 4
tab_width = 4
ij_visual_guides = none
[{*.markdown,*.md}]
indent_size = 2
tab_width = 2
ij_continuation_indent_size = 2
ij_visual_guides = none
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_keep_indents_on_empty_lines = false
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
[{*.yaml,*.yml}]
ij_visual_guides = none
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

14
frontend/.eslintrc.json Normal file
View File

@ -0,0 +1,14 @@
{
"extends": "next/core-web-vitals",
"rules": {
"@next/next/no-img-element": "off",
"jsx-a11y/alt-text": "off",
"react/display-name": "off",
"react/jsx-max-props-per-line": [
1,
{
"maximum": 1
}
]
}
}

1
frontend/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=lf

28
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
out
.next
# misc
.DS_Store
.eslintcache
.idea
/.env
/.env.local
/.env.development.local
/.env.test.local
/.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

109
frontend/README.md Executable file
View File

@ -0,0 +1,109 @@
## [Material Kit - React](https://material-kit-react.devias.io/) [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social&logo=twitter)](https://twitter.com/intent/tweet?text=%F0%9F%9A%A8Devias%20Freebie%20Alert%20-%20An%20awesome%20ready-to-use%20register%20page%20made%20with%20%23material%20%23react%0D%0Ahttps%3A%2F%2Fdevias.io%20%23createreactapp%20%23devias%20%23material%20%23freebie%20%40devias-io)
![license](https://img.shields.io/badge/license-MIT-blue.svg)
[![Material Kit - React](https://github.com/devias-io/material-kit-react/blob/main/public/assets/thumbnail.png)](https://material-kit-react.devias.io/)
> Free React Admin Dashboard made with [MUI's](https://mui.com/?ref=devias-io)
> components, [React](https://reactjs.org/?ref=devias-io) and of
> course [Next.js](https://github.com/vercel/next.js/?ref=devias-io) to boost your app development
> process!
## Demo
- [Dashboard Page](https://material-kit-react.devias.io)
- [Companies Page](https://material-kit-react.devias.io/companies)
- [Customers Page](https://material-kit-react.devias.io/customers)
- [Account Page](https://material-kit-react.devias.io/account)
- [Settings Page](https://material-kit-react.devias.io/settings)
- [Login Page](https://material-kit-react.devias.io/auth/login)
- [Register Page](https://material-kit-react.devias.io/auth/register)
## Free Figma Community File
- [Duplicate File](https://www.figma.com/community/file/1039837897183395483/Devias-Dashboard-Design-Library-Kit)
## Upgrade to PRO Version
We also have a pro version of this product which bundles even more pages and components if you want
to save more time and design efforts :)
| Free Version (this one) | [Material Kit Pro - React](https://mui.com/store/items/devias-kit-pro/) |
|-------------------------|:-------------------------------------------------------------------------|
| **9** Demo Pages | **40+** demo pages
| ✔ Mocked Authentication | ✔ Authentication with **Amplify**, **Auth0**, **JWT** and **Firebase**
| - | ✔ Dark & light mode
| - | ✔ CRA version
| - | ✔ TypeScript version - for Standard Plus and Extended license
| - | ✔ Design files (sketch & figma) - for Standard Plus and Extended license
| - | ✔ Complete users flows
## Quick start
- [Download from Github](https://github.com/devias-io/material-kit-react/archive/master.zip)
or [Download from Devias](https://devias.io/products/material-kit-react) or clone the
repo: `git clone https://github.com/devias-io/material-kit-react.git`
- Make sure your Node.js and npm versions are up to date for `React 18`
- Install dependencies: `npm install` or `yarn`
- Start the server: `npm run dev` or `yarn dev`
- Views are on: `localhost:3000`
## File Structure
Within the download you'll find the following directories and files:
```
material-kit-react
┌── .eslintrc.json
├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── next.config.js
├── package.json
├── README.md
├── public
└── src
├── components
├── contexts
├── guards
├── hocs
├── hooks
├── layouts
├── sections
├── theme
├── utils
└── pages
├── 404.js
├── _app.js
├── _document.js
├── account.js
├── companies.js
├── customers.js
├── index.js
├── products.js
└── settings.js
└── auth
├── login.js
└── register.js
```
## Resources
- More freebies like this one: <https://devias.io>
## Reporting Issues:
- [Github Issues Page](https://github.com/devias-io/react-material-dashboard/issues?ref=devias-io)
## License
- Licensed under MIT (https://github.com/devias-io/react-material-dashboard/blob/master/LICENSE.md)
## Contact Us
- Email Us: support@deviasio.zendesk.com

5
frontend/jsconfig.json Normal file
View File

@ -0,0 +1,5 @@
{
"compilerOptions": {
"baseUrl": "."
}
}

3
frontend/next.config.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
reactStrictMode: true
};

8204
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

47
frontend/package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "material-kit-react",
"version": "3.0.0",
"author": "Devias",
"licence": "MIT",
"homepage": "https://devias.io",
"private": false,
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"export": "next export",
"lint": "next lint",
"lint-fix": "next lint --fix"
},
"dependencies": {
"@emotion/cache": "11.10.5",
"@emotion/react": "11.10.6",
"@emotion/server": "11.10.0",
"@emotion/styled": "11.10.6",
"@heroicons/react": "2.0.16",
"@mui/lab": "5.0.0-alpha.120",
"@mui/material": "5.11.10",
"@mui/system": "5.11.9",
"@mui/x-date-pickers": "5.0.19",
"apexcharts": "3.37.0",
"date-fns": "2.29.3",
"formik": "2.2.9",
"next": "13.1.6",
"nprogress": "0.2.0",
"prop-types": "15.8.1",
"react": "18.2.0",
"react-apexcharts": "1.4.0",
"react-dom": "18.2.0",
"simplebar-react": "^3.2.1",
"yup": "1.0.0"
},
"devDependencies": {
"@types/node": "18.13.0",
"@types/nprogress": "0.2.0",
"@types/numeral": "2.0.2",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"eslint": "8.34.0",
"eslint-config-next": "13.1.6"
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,15 @@
{
"short_name": "Devias Kit",
"name": "Devias Kit",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,9 @@
import dynamic from 'next/dynamic';
import { styled } from '@mui/material/styles';
const ApexChart = dynamic(() => import('react-apexcharts'), {
ssr: false,
loading: () => null
});
export const Chart = styled(ApexChart)``;

View File

@ -0,0 +1,26 @@
import { useTheme } from '@mui/material/styles';
export const Logo = () => {
const theme = useTheme();
const fillColor = theme.palette.primary.main;
return (
<svg
fill="none"
height="100%"
viewBox="0 0 24 24"
width="100%"
xmlns="http://www.w3.org/2000/svg"
>
<path
opacity={0.16}
d="M7.242 11.083c.449-1.674 2.17-3.394 3.843-3.843l10.434-2.796c1.673-.448 2.666.545 2.218 2.218L20.94 17.096c-.449 1.674-2.17 3.394-3.843 3.843L6.664 23.735c-1.673.448-2.666-.545-2.218-2.218l2.796-10.434Z"
fill={fillColor}
/>
<path
d="M3.06 6.9c.448-1.674 2.168-3.394 3.842-3.843L17.336.261c1.673-.448 2.667.545 2.218 2.218l-2.796 10.434c-.449 1.674-2.169 3.394-3.843 3.843L2.481 19.552C.808 20-.185 19.007.263 17.334L3.06 6.9Z"
fill={fillColor}
/>
</svg>
);
};

View File

@ -0,0 +1,4 @@
import SimpleBar from 'simplebar-react';
import { styled } from '@mui/material/styles';
export const Scrollbar = styled(SimpleBar)``;

View File

@ -0,0 +1,58 @@
import PropTypes from 'prop-types';
import { styled } from '@mui/material/styles';
const SeverityPillRoot = styled('span')(({ theme, ownerState }) => {
const backgroundColor = theme.palette[ownerState.color].alpha12;
const color = theme.palette.mode === 'dark'
? theme.palette[ownerState.color].main
: theme.palette[ownerState.color].dark;
return {
alignItems: 'center',
backgroundColor,
borderRadius: 12,
color,
cursor: 'default',
display: 'inline-flex',
flexGrow: 0,
flexShrink: 0,
fontFamily: theme.typography.fontFamily,
fontSize: theme.typography.pxToRem(12),
lineHeight: 2,
fontWeight: 600,
justifyContent: 'center',
letterSpacing: 0.5,
minWidth: 20,
paddingLeft: theme.spacing(1),
paddingRight: theme.spacing(1),
textTransform: 'uppercase',
whiteSpace: 'nowrap'
};
});
export const SeverityPill = (props) => {
const { color = 'primary', children, ...other } = props;
const ownerState = { color };
return (
<SeverityPillRoot
ownerState={ownerState}
{...other}
>
{children}
</SeverityPillRoot>
);
};
SeverityPill.propTypes = {
children: PropTypes.node,
color: PropTypes.oneOf([
'primary',
'secondary',
'error',
'info',
'warning',
'success'
])
};

View File

@ -0,0 +1,185 @@
import { createContext, useContext, useEffect, useReducer, useRef } from 'react';
import PropTypes from 'prop-types';
const HANDLERS = {
INITIALIZE: 'INITIALIZE',
SIGN_IN: 'SIGN_IN',
SIGN_OUT: 'SIGN_OUT'
};
const initialState = {
isAuthenticated: false,
isLoading: true,
user: null
};
const handlers = {
[HANDLERS.INITIALIZE]: (state, action) => {
const user = action.payload;
return {
...state,
...(
// if payload (user) is provided, then is authenticated
user
? ({
isAuthenticated: true,
isLoading: false,
user
})
: ({
isLoading: false
})
)
};
},
[HANDLERS.SIGN_IN]: (state, action) => {
const user = action.payload;
return {
...state,
isAuthenticated: true,
user
};
},
[HANDLERS.SIGN_OUT]: (state) => {
return {
...state,
isAuthenticated: false,
user: null
};
}
};
const reducer = (state, action) => (
handlers[action.type] ? handlers[action.type](state, action) : state
);
// The role of this context is to propagate authentication state through the App tree.
export const AuthContext = createContext({ undefined });
export const AuthProvider = (props) => {
const { children } = props;
const [state, dispatch] = useReducer(reducer, initialState);
const initialized = useRef(false);
const initialize = async () => {
// Prevent from calling twice in development mode with React.StrictMode enabled
if (initialized.current) {
return;
}
initialized.current = true;
let isAuthenticated = false;
try {
isAuthenticated = window.sessionStorage.getItem('authenticated') === 'true';
} catch (err) {
console.error(err);
}
if (isAuthenticated) {
const user = {
id: '5e86809283e28b96d2d38537',
avatar: '/assets/avatars/avatar-anika-visser.png',
name: 'Anika Visser',
email: 'anika.visser@devias.io'
};
dispatch({
type: HANDLERS.INITIALIZE,
payload: user
});
} else {
dispatch({
type: HANDLERS.INITIALIZE
});
}
};
useEffect(
() => {
initialize();
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const skip = () => {
try {
window.sessionStorage.setItem('authenticated', 'true');
} catch (err) {
console.error(err);
}
const user = {
id: '5e86809283e28b96d2d38537',
avatar: '/assets/avatars/avatar-anika-visser.png',
name: 'Anika Visser',
email: 'anika.visser@devias.io'
};
dispatch({
type: HANDLERS.SIGN_IN,
payload: user
});
};
const signIn = async (email, password) => {
if (email !== 'demo@devias.io' || password !== 'Password123!') {
throw new Error('Please check your email and password');
}
try {
window.sessionStorage.setItem('authenticated', 'true');
} catch (err) {
console.error(err);
}
const user = {
id: '5e86809283e28b96d2d38537',
avatar: '/assets/avatars/avatar-anika-visser.png',
name: 'Anika Visser',
email: 'anika.visser@devias.io'
};
dispatch({
type: HANDLERS.SIGN_IN,
payload: user
});
};
const signUp = async (email, name, password) => {
throw new Error('Sign up is not implemented');
};
const signOut = () => {
dispatch({
type: HANDLERS.SIGN_OUT
});
};
return (
<AuthContext.Provider
value={{
...state,
skip,
signIn,
signUp,
signOut
}}
>
{children}
</AuthContext.Provider>
);
};
AuthProvider.propTypes = {
children: PropTypes.node
};
export const AuthConsumer = AuthContext.Consumer;
export const useAuthContext = () => useContext(AuthContext);

View File

@ -0,0 +1,57 @@
import { useEffect, useRef, useState } from 'react';
import { useRouter } from 'next/router';
import PropTypes from 'prop-types';
import { useAuthContext } from 'src/contexts/auth-context';
export const AuthGuard = (props) => {
const { children } = props;
const router = useRouter();
const { isAuthenticated } = useAuthContext();
const ignore = useRef(false);
const [checked, setChecked] = useState(false);
// Only do authentication check on component mount.
// This flow allows you to manually redirect the user after sign-out, otherwise this will be
// triggered and will automatically redirect to sign-in page.
useEffect(
() => {
if (!router.isReady) {
return;
}
// Prevent from calling twice in development mode with React.StrictMode enabled
if (ignore.current) {
return;
}
ignore.current = true;
if (!isAuthenticated) {
console.log('Not authenticated, redirecting');
router
.replace({
pathname: '/auth/login',
query: router.asPath !== '/' ? { continueUrl: router.asPath } : undefined
})
.catch(console.error);
} else {
setChecked(true);
}
},
[router.isReady]
);
if (!checked) {
return null;
}
// If got here, it means that the redirect did not occur, and that tells us that the user is
// authenticated / authorized.
return children;
};
AuthGuard.propTypes = {
children: PropTypes.node
};

View File

@ -0,0 +1,7 @@
import { AuthGuard } from 'src/guards/auth-guard';
export const withAuthGuard = (Component) => (props) => (
<AuthGuard>
<Component {...props} />
</AuthGuard>
);

View File

@ -0,0 +1,4 @@
import { useContext } from 'react';
import { AuthContext } from 'src/contexts/auth-context';
export const useAuth = () => useContext(AuthContext);

View File

@ -0,0 +1,10 @@
export const useMockedUser = () => {
// To get the user from the authContext, you can use
// `const { user } = useAuth();`
return {
id: '5e86809283e28b96d2d38537',
avatar: '/assets/avatars/avatar-anika-visser.png',
name: 'Anika Visser',
email: 'anika.visser@devias.io'
};
};

View File

@ -0,0 +1,17 @@
import { useEffect } from 'react';
import Router from 'next/router';
import nProgress from 'nprogress';
export function useNProgress() {
useEffect(() => {
Router.events.on('routeChangeStart', nProgress.start);
Router.events.on('routeChangeError', nProgress.done);
Router.events.on('routeChangeComplete', nProgress.done);
return () => {
Router.events.off('routeChangeStart', nProgress.start);
Router.events.off('routeChangeError', nProgress.done);
Router.events.off('routeChangeComplete', nProgress.done);
};
}, []);
}

View File

@ -0,0 +1,26 @@
import { useCallback, useRef, useState } from 'react';
export function usePopover() {
const anchorRef = useRef(null);
const [open, setOpen] = useState(false);
const handleOpen = useCallback(() => {
setOpen(true);
}, []);
const handleClose = useCallback(() => {
setOpen(false);
}, []);
const handleToggle = useCallback(() => {
setOpen((prevState) => !prevState);
}, []);
return {
anchorRef,
handleClose,
handleOpen,
handleToggle,
open
};
}

View File

@ -0,0 +1,35 @@
import { useCallback, useEffect, useState } from 'react';
export const useSelection = (items = []) => {
const [selected, setSelected] = useState([]);
useEffect(() => {
setSelected([]);
}, [items]);
const handleSelectAll = useCallback(() => {
setSelected([...items]);
}, [items]);
const handleSelectOne = useCallback((item) => {
setSelected((prevState) => [...prevState, item]);
}, []);
const handleDeselectAll = useCallback(() => {
setSelected([]);
}, []);
const handleDeselectOne = useCallback((item) => {
setSelected((prevState) => {
return prevState.filter((_item) => _item !== item);
});
}, []);
return {
handleDeselectAll,
handleDeselectOne,
handleSelectAll,
handleSelectOne,
selected
};
};

View File

@ -0,0 +1,111 @@
import PropTypes from 'prop-types';
import NextLink from 'next/link';
import { Box, Typography, Unstable_Grid2 as Grid } from '@mui/material';
import { Logo } from 'src/components/logo';
// TODO: Change subtitle text
export const Layout = (props) => {
const { children } = props;
return (
<Box
component="main"
sx={{
display: 'flex',
flex: '1 1 auto'
}}
>
<Grid
container
sx={{ flex: '1 1 auto' }}
>
<Grid
xs={12}
lg={6}
sx={{
backgroundColor: 'background.paper',
display: 'flex',
flexDirection: 'column',
position: 'relative'
}}
>
<Box
component="header"
sx={{
left: 0,
p: 3,
position: 'fixed',
top: 0,
width: '100%'
}}
>
<Box
component={NextLink}
href="/"
sx={{
display: 'inline-flex',
height: 32,
width: 32
}}
>
<Logo />
</Box>
</Box>
{children}
</Grid>
<Grid
xs={12}
lg={6}
sx={{
alignItems: 'center',
background: 'radial-gradient(50% 50% at 50% 50%, #122647 0%, #090E23 100%)',
color: 'white',
display: 'flex',
justifyContent: 'center',
'& img': {
maxWidth: '100%'
}
}}
>
<Box sx={{ p: 3 }}>
<Typography
align="center"
color="inherit"
sx={{
fontSize: '24px',
lineHeight: '32px',
mb: 1
}}
variant="h1"
>
Welcome to{' '}
<Box
component="a"
sx={{ color: '#15B79E' }}
target="_blank"
>
Devias Kit
</Box>
</Typography>
<Typography
align="center"
sx={{ mb: 3 }}
variant="subtitle1"
>
A professional kit that comes with ready-to-use MUI components.
</Typography>
<img
alt=""
src="/assets/auth-illustration.svg"
/>
</Box>
</Grid>
</Grid>
</Box>
);
};
Layout.prototypes = {
children: PropTypes.node
};

View File

@ -0,0 +1,71 @@
import { useCallback } from 'react';
import { useRouter } from 'next/navigation';
import PropTypes from 'prop-types';
import { Box, Divider, MenuItem, MenuList, Popover, Typography } from '@mui/material';
import { useAuth } from 'src/hooks/use-auth';
export const AccountPopover = (props) => {
const { anchorEl, onClose, open } = props;
const router = useRouter();
const auth = useAuth();
const handleSignOut = useCallback(
() => {
onClose?.();
auth.signOut();
router.push('/auth/login');
},
[onClose, auth, router]
);
return (
<Popover
anchorEl={anchorEl}
anchorOrigin={{
horizontal: 'left',
vertical: 'bottom'
}}
onClose={onClose}
open={open}
PaperProps={{ sx: { width: 200 } }}
>
<Box
sx={{
py: 1.5,
px: 2
}}
>
<Typography variant="overline">
Account
</Typography>
<Typography
color="text.secondary"
variant="body2"
>
Anika Visser
</Typography>
</Box>
<Divider />
<MenuList
disablePadding
dense
sx={{
p: '8px',
'& > *': {
borderRadius: 1
}
}}
>
<MenuItem onClick={handleSignOut}>
Sign out
</MenuItem>
</MenuList>
</Popover>
);
};
AccountPopover.propTypes = {
anchorEl: PropTypes.any,
onClose: PropTypes.func,
open: PropTypes.bool.isRequired
};

View File

@ -0,0 +1,84 @@
import ChartBarIcon from '@heroicons/react/24/solid/ChartBarIcon';
import CogIcon from '@heroicons/react/24/solid/CogIcon';
import LockClosedIcon from '@heroicons/react/24/solid/LockClosedIcon';
import ShoppingBagIcon from '@heroicons/react/24/solid/ShoppingBagIcon';
import UserIcon from '@heroicons/react/24/solid/UserIcon';
import UserPlusIcon from '@heroicons/react/24/solid/UserPlusIcon';
import UsersIcon from '@heroicons/react/24/solid/UsersIcon';
import XCircleIcon from '@heroicons/react/24/solid/XCircleIcon';
import { SvgIcon } from '@mui/material';
export const items = [
{
title: 'Overview',
path: '/',
icon: (
<SvgIcon fontSize="small">
<ChartBarIcon />
</SvgIcon>
)
},
{
title: 'Customers',
path: '/customers',
icon: (
<SvgIcon fontSize="small">
<UsersIcon />
</SvgIcon>
)
},
{
title: 'Companies',
path: '/companies',
icon: (
<SvgIcon fontSize="small">
<ShoppingBagIcon />
</SvgIcon>
)
},
{
title: 'Account',
path: '/account',
icon: (
<SvgIcon fontSize="small">
<UserIcon />
</SvgIcon>
)
},
{
title: 'Settings',
path: '/settings',
icon: (
<SvgIcon fontSize="small">
<CogIcon />
</SvgIcon>
)
},
{
title: 'Login',
path: '/auth/login',
icon: (
<SvgIcon fontSize="small">
<LockClosedIcon />
</SvgIcon>
)
},
{
title: 'Register',
path: '/auth/register',
icon: (
<SvgIcon fontSize="small">
<UserPlusIcon />
</SvgIcon>
)
},
{
title: 'Error',
path: '/404',
icon: (
<SvgIcon fontSize="small">
<XCircleIcon />
</SvgIcon>
)
}
];

View File

@ -0,0 +1,54 @@
import { useEffect, useState } from 'react';
import { styled } from '@mui/material/styles';
import { withAuthGuard } from 'src/hocs/with-auth-guard';
import { SideNav } from './side-nav';
import { TopNav } from './top-nav';
import { usePathname } from 'next/navigation';
const SIDE_NAV_WIDTH = 280;
const LayoutRoot = styled('div')(({ theme }) => ({
display: 'flex',
flex: '1 1 auto',
maxWidth: '100%',
[theme.breakpoints.up('lg')]: {
paddingLeft: SIDE_NAV_WIDTH
}
}));
const LayoutContainer = styled('div')({
display: 'flex',
flex: '1 1 auto',
flexDirection: 'column',
width: '100%'
});
export const Layout = withAuthGuard((props) => {
const { children } = props;
const pathname = usePathname();
const [openNav, setOpenNav] = useState(true);
useEffect(
() => {
if (openNav) {
setOpenNav(false);
}
},
[pathname, openNav]
);
return (
<>
<TopNav onNavOpen={() => setOpenNav(true)} />
<SideNav
onClose={() => setOpenNav(false)}
open={openNav}
/>
<LayoutRoot>
<LayoutContainer>
{children}
</LayoutContainer>
</LayoutRoot>
</>
);
});

View File

@ -0,0 +1,92 @@
import NextLink from 'next/link';
import PropTypes from 'prop-types';
import { Box, ButtonBase } from '@mui/material';
export const SideNavItem = (props) => {
const { active = false, disabled, external, icon, path, title } = props;
const linkProps = path
? external
? {
component: 'a',
href: path,
target: '_blank'
}
: {
component: NextLink,
href: path
}
: {};
return (
<li>
<ButtonBase
sx={{
alignItems: 'center',
borderRadius: 1,
display: 'flex',
justifyContent: 'flex-start',
pl: '16px',
pr: '16px',
py: '6px',
textAlign: 'left',
width: '100%',
...(active && {
backgroundColor: 'rgba(255, 255, 255, 0.04)'
}),
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.04)'
}
}}
{...linkProps}
>
{icon && (
<Box
component="span"
sx={{
alignItems: 'center',
color: 'neutral.400',
display: 'inline-flex',
justifyContent: 'center',
mr: 2,
...(active && {
color: 'primary.main'
})
}}
>
{icon}
</Box>
)}
<Box
component="span"
sx={{
color: 'neutral.400',
flexGrow: 1,
fontFamily: (theme) => theme.typography.fontFamily,
fontSize: 14,
fontWeight: 600,
lineHeight: '24px',
whiteSpace: 'nowrap',
...(active && {
color: 'common.white'
}),
...(disabled && {
color: 'neutral.500'
})
}}
>
{title}
</Box>
</ButtonBase>
</li>
);
};
SideNavItem.propTypes = {
active: PropTypes.bool,
disabled: PropTypes.bool,
external: PropTypes.bool,
icon: PropTypes.node,
path: PropTypes.string,
title: PropTypes.string.isRequired
};

View File

@ -0,0 +1,223 @@
import NextLink from 'next/link';
import { usePathname } from 'next/navigation';
import PropTypes from 'prop-types';
import ArrowTopRightOnSquareIcon from '@heroicons/react/24/solid/ArrowTopRightOnSquareIcon';
import ChevronUpDownIcon from '@heroicons/react/24/solid/ChevronUpDownIcon';
import {
Box,
Button,
Divider,
Drawer,
Stack,
SvgIcon,
Typography,
useMediaQuery
} from '@mui/material';
import { Logo } from 'src/components/logo';
import { Scrollbar } from 'src/components/scrollbar';
import { items } from './config';
import { SideNavItem } from './side-nav-item';
export const SideNav = (props) => {
const { open, onClose } = props;
const pathname = usePathname();
const lgUp = useMediaQuery((theme) => theme.breakpoints.up('lg'));
const content = (
<Scrollbar
sx={{
height: '100%',
'& .simplebar-content': {
height: '100%'
},
'& .simplebar-scrollbar:before': {
background: 'neutral.400'
}
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
height: '100%'
}}
>
<Box sx={{ p: 3 }}>
<Box
component={NextLink}
href="/"
sx={{
display: 'inline-flex',
height: 32,
width: 32
}}
>
<Logo />
</Box>
<Box
sx={{
alignItems: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.04)',
borderRadius: 1,
cursor: 'pointer',
display: 'flex',
justifyContent: 'space-between',
mt: 2,
p: '12px'
}}
>
<div>
<Typography
color="inherit"
variant="subtitle1"
>
Devias
</Typography>
<Typography
color="neutral.400"
variant="body2"
>
Production
</Typography>
</div>
<SvgIcon
fontSize="small"
sx={{ color: 'neutral.500' }}
>
<ChevronUpDownIcon />
</SvgIcon>
</Box>
</Box>
<Divider sx={{ borderColor: 'neutral.700' }} />
<Box
component="nav"
sx={{
flexGrow: 1,
px: 2,
py: 3
}}
>
<Stack
component="ul"
spacing={0.5}
sx={{
listStyle: 'none',
p: 0,
m: 0
}}
>
{items.map((item) => {
const active = item.path ? (pathname === item.path) : false;
return (
<SideNavItem
active={active}
disabled={item.disabled}
external={item.external}
icon={item.icon}
key={item.title}
path={item.path}
title={item.title}
/>
);
})}
</Stack>
</Box>
<Divider sx={{ borderColor: 'neutral.700' }} />
<Box
sx={{
px: 2,
py: 3
}}
>
<Typography
color="neutral.100"
variant="subtitle2"
>
Need more features?
</Typography>
<Typography
color="neutral.500"
variant="body2"
>
Check out our Pro solution template.
</Typography>
<Box
sx={{
display: 'flex',
mt: 2,
mx: 'auto',
width: '160px',
'& img': {
width: '100%'
}
}}
>
<img
alt="Go to pro"
src="/assets/devias-kit-pro.png"
/>
</Box>
<Button
component="a"
endIcon={(
<SvgIcon fontSize="small">
<ArrowTopRightOnSquareIcon />
</SvgIcon>
)}
fullWidth
href="https://material-kit-pro-react.devias.io/"
sx={{ mt: 2 }}
target="_blank"
variant="contained"
>
Pro Live Preview
</Button>
</Box>
</Box>
</Scrollbar>
);
if (lgUp) {
return (
<Drawer
anchor="left"
open
PaperProps={{
sx: {
backgroundColor: 'neutral.800',
color: 'common.white',
width: 280
}
}}
variant="permanent"
>
{content}
</Drawer>
);
}
return (
<Drawer
anchor="left"
onClose={onClose}
open={open}
PaperProps={{
sx: {
backgroundColor: 'neutral.800',
color: 'common.white',
width: 280
}
}}
sx={{ zIndex: (theme) => theme.zIndex.appBar + 100 }}
variant="temporary"
>
{content}
</Drawer>
);
};
SideNav.propTypes = {
onClose: PropTypes.func,
open: PropTypes.bool
};

View File

@ -0,0 +1,125 @@
import PropTypes from 'prop-types';
import BellIcon from '@heroicons/react/24/solid/BellIcon';
import UsersIcon from '@heroicons/react/24/solid/UsersIcon';
import Bars3Icon from '@heroicons/react/24/solid/Bars3Icon';
import MagnifyingGlassIcon from '@heroicons/react/24/solid/MagnifyingGlassIcon';
import {
Avatar,
Badge,
Box,
IconButton,
Stack,
SvgIcon,
Tooltip,
useMediaQuery
} from '@mui/material';
import { alpha } from '@mui/material/styles';
import { usePopover } from 'src/hooks/use-popover';
import { AccountPopover } from './account-popover';
const SIDE_NAV_WIDTH = 280;
const TOP_NAV_HEIGHT = 64;
export const TopNav = (props) => {
const { onNavOpen } = props;
const lgUp = useMediaQuery((theme) => theme.breakpoints.up('lg'));
const accountPopover = usePopover();
return (
<>
<Box
component="header"
sx={{
backdropFilter: 'blur(6px)',
backgroundColor: (theme) => alpha(theme.palette.background.default, 0.8),
position: 'sticky',
left: {
lg: `${SIDE_NAV_WIDTH}px`
},
top: 0,
width: {
lg: `calc(100% - ${SIDE_NAV_WIDTH}px)`
},
zIndex: (theme) => theme.zIndex.appBar
}}
>
<Stack
alignItems="center"
direction="row"
justifyContent="space-between"
spacing={2}
sx={{
minHeight: TOP_NAV_HEIGHT,
px: 2
}}
>
<Stack
alignItems="center"
direction="row"
spacing={2}
>
{!lgUp && (
<IconButton onClick={onNavOpen}>
<SvgIcon fontSize="small">
<Bars3Icon />
</SvgIcon>
</IconButton>
)}
<Tooltip title="Search">
<IconButton>
<SvgIcon fontSize="small">
<MagnifyingGlassIcon />
</SvgIcon>
</IconButton>
</Tooltip>
</Stack>
<Stack
alignItems="center"
direction="row"
spacing={2}
>
<Tooltip title="Contacts">
<IconButton>
<SvgIcon fontSize="small">
<UsersIcon />
</SvgIcon>
</IconButton>
</Tooltip>
<Tooltip title="Notifications">
<IconButton>
<Badge
badgeContent={4}
color="success"
variant="dot"
>
<SvgIcon fontSize="small">
<BellIcon />
</SvgIcon>
</Badge>
</IconButton>
</Tooltip>
<Avatar
onClick={accountPopover.handleOpen}
ref={accountPopover.anchorRef}
sx={{
cursor: 'pointer',
height: 40,
width: 40
}}
src="/assets/avatars/avatar-anika-visser.png"
/>
</Stack>
</Stack>
</Box>
<AccountPopover
anchorEl={accountPopover.anchorRef.current}
open={accountPopover.open}
onClose={accountPopover.handleClose}
/>
</>
);
};
TopNav.propTypes = {
onNavOpen: PropTypes.func
};

80
frontend/src/pages/404.js Normal file
View File

@ -0,0 +1,80 @@
import Head from 'next/head';
import NextLink from 'next/link';
import ArrowLeftIcon from '@heroicons/react/24/solid/ArrowLeftIcon';
import { Box, Button, Container, SvgIcon, Typography } from '@mui/material';
const Page = () => (
<>
<Head>
<title>
404 | Devias Kit
</title>
</Head>
<Box
component="main"
sx={{
alignItems: 'center',
display: 'flex',
flexGrow: 1,
minHeight: '100%'
}}
>
<Container maxWidth="md">
<Box
sx={{
alignItems: 'center',
display: 'flex',
flexDirection: 'column'
}}
>
<Box
sx={{
mb: 3,
textAlign: 'center'
}}
>
<img
alt="Under development"
src="/assets/errors/error-404.png"
style={{
display: 'inline-block',
maxWidth: '100%',
width: 400
}}
/>
</Box>
<Typography
align="center"
sx={{ mb: 3 }}
variant="h3"
>
404: The page you are looking for isnt here
</Typography>
<Typography
align="center"
color="text.secondary"
variant="body1"
>
You either tried some shady route or you came here by mistake.
Whichever it is, try using the navigation
</Typography>
<Button
component={NextLink}
href="/"
startIcon={(
<SvgIcon fontSize="small">
<ArrowLeftIcon />
</SvgIcon>
)}
sx={{ mt: 3 }}
variant="contained"
>
Go back to dashboard
</Button>
</Box>
</Container>
</Box>
</>
);
export default Page;

View File

@ -0,0 +1,55 @@
import Head from 'next/head';
import { CacheProvider } from '@emotion/react';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { CssBaseline } from '@mui/material';
import { ThemeProvider } from '@mui/material/styles';
import { AuthConsumer, AuthProvider } from 'src/contexts/auth-context';
import { useNProgress } from 'src/hooks/use-nprogress';
import { createTheme } from 'src/theme';
import { createEmotionCache } from 'src/utils/create-emotion-cache';
import 'simplebar-react/dist/simplebar.min.css';
const clientSideEmotionCache = createEmotionCache();
const SplashScreen = () => null;
const App = (props) => {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
useNProgress();
const getLayout = Component.getLayout ?? ((page) => page);
const theme = createTheme();
return (
<CacheProvider value={emotionCache}>
<Head>
<title>
Devias Kit
</title>
<meta
name="viewport"
content="initial-scale=1, width=device-width"
/>
</Head>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<AuthProvider>
<ThemeProvider theme={theme}>
<CssBaseline />
<AuthConsumer>
{
(auth) => auth.isLoading
? <SplashScreen />
: getLayout(<Component {...pageProps} />)
}
</AuthConsumer>
</ThemeProvider>
</AuthProvider>
</LocalizationProvider>
</CacheProvider>
);
};
export default App;

View File

@ -0,0 +1,105 @@
import { Children } from 'react';
import Document, { Head, Html, Main, NextScript } from 'next/document';
import createEmotionServer from '@emotion/server/create-instance';
import { createEmotionCache } from 'src/utils/create-emotion-cache';
const Favicon = () => (
<>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
href="/favicon.ico"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
</>
);
const Fonts = () => (
<>
<link
rel="preconnect"
href="https://fonts.googleapis.com"
/>
<link
rel="preconnect"
href="https://fonts.gstatic.com"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400&display=swap"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@600;700&display=swap"
/>
</>
);
class CustomDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<Favicon />
<Fonts />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
CustomDocument.getInitialProps = async (ctx) => {
const originalRenderPage = ctx.renderPage;
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
ctx.renderPage = () => originalRenderPage({
enhanceApp: (App) => (props) => (
<App
emotionCache={cache}
{...props}
/>
)
});
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map((style) => (
<style
data-emotion={`${style.key} ${style.ids.join(' ')}`}
key={style.key}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));
return {
...initialProps,
styles: [...Children.toArray(initialProps.styles), ...emotionStyleTags]
};
};
export default CustomDocument;

View File

@ -0,0 +1,61 @@
import Head from 'next/head';
import { Box, Container, Stack, Typography, Unstable_Grid2 as Grid } from '@mui/material';
import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout';
import { AccountProfile } from 'src/sections/account/account-profile';
import { AccountProfileDetails } from 'src/sections/account/account-profile-details';
const Page = () => (
<>
<Head>
<title>
Account | Devias Kit
</title>
</Head>
<Box
component="main"
sx={{
flexGrow: 1,
py: 8
}}
>
<Container maxWidth="lg">
<Stack spacing={3}>
<div>
<Typography variant="h4">
Account
</Typography>
</div>
<div>
<Grid
container
spacing={3}
>
<Grid
xs={12}
md={6}
lg={4}
>
<AccountProfile />
</Grid>
<Grid
xs={12}
md={6}
lg={8}
>
<AccountProfileDetails />
</Grid>
</Grid>
</div>
</Stack>
</Container>
</Box>
</>
);
Page.getLayout = (page) => (
<DashboardLayout>
{page}
</DashboardLayout>
);
export default Page;

View File

@ -0,0 +1,227 @@
import { useCallback, useState } from 'react';
import Head from 'next/head';
import NextLink from 'next/link';
import { useRouter } from 'next/navigation';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import {
Alert,
Box,
Button,
FormHelperText,
Link,
Stack,
Tab,
Tabs,
TextField,
Typography
} from '@mui/material';
import { useAuth } from 'src/hooks/use-auth';
import { Layout as AuthLayout } from 'src/layouts/auth/layout';
const Page = () => {
const router = useRouter();
const auth = useAuth();
const [method, setMethod] = useState('email');
const formik = useFormik({
initialValues: {
email: 'demo@devias.io',
password: 'Password123!',
submit: null
},
validationSchema: Yup.object({
email: Yup
.string()
.email('Must be a valid email')
.max(255)
.required('Email is required'),
password: Yup
.string()
.max(255)
.required('Password is required')
}),
onSubmit: async (values, helpers) => {
try {
await auth.signIn(values.email, values.password);
router.push('/');
} catch (err) {
helpers.setStatus({ success: false });
helpers.setErrors({ submit: err.message });
helpers.setSubmitting(false);
}
}
});
const handleMethodChange = useCallback(
(event, value) => {
setMethod(value);
},
[]
);
const handleSkip = useCallback(
() => {
auth.skip();
router.push('/');
},
[auth, router]
);
return (
<>
<Head>
<title>
Login | Devias Kit
</title>
</Head>
<Box
sx={{
backgroundColor: 'background.paper',
flex: '1 1 auto',
alignItems: 'center',
display: 'flex',
justifyContent: 'center'
}}
>
<Box
sx={{
maxWidth: 550,
px: 3,
py: '100px',
width: '100%'
}}
>
<div>
<Stack
spacing={1}
sx={{ mb: 3 }}
>
<Typography variant="h4">
Login
</Typography>
<Typography
color="text.secondary"
variant="body2"
>
Don&apos;t have an account?
&nbsp;
<Link
component={NextLink}
href="/auth/register"
underline="hover"
variant="subtitle2"
>
Register
</Link>
</Typography>
</Stack>
<Tabs
onChange={handleMethodChange}
sx={{ mb: 3 }}
value={method}
>
<Tab
label="Email"
value="email"
/>
<Tab
label="Phone Number"
value="phoneNumber"
/>
</Tabs>
{method === 'email' && (
<form
noValidate
onSubmit={formik.handleSubmit}
>
<Stack spacing={3}>
<TextField
error={!!(formik.touched.email && formik.errors.email)}
fullWidth
helperText={formik.touched.email && formik.errors.email}
label="Email Address"
name="email"
onBlur={formik.handleBlur}
onChange={formik.handleChange}
type="email"
value={formik.values.email}
/>
<TextField
error={!!(formik.touched.password && formik.errors.password)}
fullWidth
helperText={formik.touched.password && formik.errors.password}
label="Password"
name="password"
onBlur={formik.handleBlur}
onChange={formik.handleChange}
type="password"
value={formik.values.password}
/>
</Stack>
<FormHelperText sx={{ mt: 1 }}>
Optionally you can skip.
</FormHelperText>
{formik.errors.submit && (
<Typography
color="error"
sx={{ mt: 3 }}
variant="body2"
>
{formik.errors.submit}
</Typography>
)}
<Button
fullWidth
size="large"
sx={{ mt: 3 }}
type="submit"
variant="contained"
>
Continue
</Button>
<Button
fullWidth
size="large"
sx={{ mt: 3 }}
onClick={handleSkip}
>
Skip authentication
</Button>
<Alert
color="primary"
severity="info"
sx={{ mt: 3 }}
>
<div>
You can use <b>demo@devias.io</b> and password <b>Password123!</b>
</div>
</Alert>
</form>
)}
{method === 'phoneNumber' && (
<div>
<Typography
sx={{ mb: 1 }}
variant="h6"
>
Not available in the demo
</Typography>
<Typography color="text.secondary">
To prevent unnecessary costs we disabled this feature in the demo.
</Typography>
</div>
)}
</div>
</Box>
</Box>
</>
);
};
Page.getLayout = (page) => (
<AuthLayout>
{page}
</AuthLayout>
);
export default Page;

View File

@ -0,0 +1,164 @@
import Head from 'next/head';
import NextLink from 'next/link';
import { useRouter } from 'next/navigation';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { Box, Button, Link, Stack, TextField, Typography } from '@mui/material';
import { useAuth } from 'src/hooks/use-auth';
import { Layout as AuthLayout } from 'src/layouts/auth/layout';
const Page = () => {
const router = useRouter();
const auth = useAuth();
const formik = useFormik({
initialValues: {
email: '',
name: '',
password: '',
submit: null
},
validationSchema: Yup.object({
email: Yup
.string()
.email('Must be a valid email')
.max(255)
.required('Email is required'),
name: Yup
.string()
.max(255)
.required('Name is required'),
password: Yup
.string()
.max(255)
.required('Password is required')
}),
onSubmit: async (values, helpers) => {
try {
await auth.signUp(values.email, values.name, values.password);
router.push('/');
} catch (err) {
helpers.setStatus({ success: false });
helpers.setErrors({ submit: err.message });
helpers.setSubmitting(false);
}
}
});
return (
<>
<Head>
<title>
Register | Devias Kit
</title>
</Head>
<Box
sx={{
flex: '1 1 auto',
alignItems: 'center',
display: 'flex',
justifyContent: 'center'
}}
>
<Box
sx={{
maxWidth: 550,
px: 3,
py: '100px',
width: '100%'
}}
>
<div>
<Stack
spacing={1}
sx={{ mb: 3 }}
>
<Typography variant="h4">
Register
</Typography>
<Typography
color="text.secondary"
variant="body2"
>
Already have an account?
&nbsp;
<Link
component={NextLink}
href="/auth/login"
underline="hover"
variant="subtitle2"
>
Log in
</Link>
</Typography>
</Stack>
<form
noValidate
onSubmit={formik.handleSubmit}
>
<Stack spacing={3}>
<TextField
error={!!(formik.touched.name && formik.errors.name)}
fullWidth
helperText={formik.touched.name && formik.errors.name}
label="Name"
name="name"
onBlur={formik.handleBlur}
onChange={formik.handleChange}
value={formik.values.name}
/>
<TextField
error={!!(formik.touched.email && formik.errors.email)}
fullWidth
helperText={formik.touched.email && formik.errors.email}
label="Email Address"
name="email"
onBlur={formik.handleBlur}
onChange={formik.handleChange}
type="email"
value={formik.values.email}
/>
<TextField
error={!!(formik.touched.password && formik.errors.password)}
fullWidth
helperText={formik.touched.password && formik.errors.password}
label="Password"
name="password"
onBlur={formik.handleBlur}
onChange={formik.handleChange}
type="password"
value={formik.values.password}
/>
</Stack>
{formik.errors.submit && (
<Typography
color="error"
sx={{ mt: 3 }}
variant="body2"
>
{formik.errors.submit}
</Typography>
)}
<Button
fullWidth
size="large"
sx={{ mt: 3 }}
type="submit"
variant="contained"
>
Continue
</Button>
</form>
</div>
</Box>
</Box>
</>
);
};
Page.getLayout = (page) => (
<AuthLayout>
{page}
</AuthLayout>
);
export default Page;

View File

@ -0,0 +1,174 @@
import Head from 'next/head';
import ArrowUpOnSquareIcon from '@heroicons/react/24/solid/ArrowUpOnSquareIcon';
import ArrowDownOnSquareIcon from '@heroicons/react/24/solid/ArrowDownOnSquareIcon';
import PlusIcon from '@heroicons/react/24/solid/PlusIcon';
import {
Box,
Button,
Container,
Pagination,
Stack,
SvgIcon,
Typography,
Unstable_Grid2 as Grid
} from '@mui/material';
import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout';
import { CompanyCard } from 'src/sections/companies/company-card';
import { CompaniesSearch } from 'src/sections/companies/companies-search';
const companies = [
{
id: '2569ce0d517a7f06d3ea1f24',
createdAt: '27/03/2019',
description: 'Dropbox is a file hosting service that offers cloud storage, file synchronization, a personal cloud.',
logo: '/assets/logos/logo-dropbox.png',
title: 'Dropbox',
downloads: '594'
},
{
id: 'ed2b900870ceba72d203ec15',
createdAt: '31/03/2019',
description: 'Medium is an online publishing platform developed by Evan Williams, and launched in August 2012.',
logo: '/assets/logos/logo-medium.png',
title: 'Medium Corporation',
downloads: '625'
},
{
id: 'a033e38768c82fca90df3db7',
createdAt: '03/04/2019',
description: 'Slack is a cloud-based set of team collaboration tools and services, founded by Stewart Butterfield.',
logo: '/assets/logos/logo-slack.png',
title: 'Slack',
downloads: '857'
},
{
id: '1efecb2bf6a51def9869ab0f',
createdAt: '04/04/2019',
description: 'Lyft is an on-demand transportation company based in San Francisco, California.',
logo: '/assets/logos/logo-lyft.png',
title: 'Lyft',
downloads: '406'
},
{
id: '1ed68149f65fbc6089b5fd07',
createdAt: '04/04/2019',
description: 'GitHub is a web-based hosting service for version control of code using Git.',
logo: '/assets/logos/logo-github.png',
title: 'GitHub',
downloads: '835'
},
{
id: '5dab321376eff6177407e887',
createdAt: '04/04/2019',
description: 'Squarespace provides software as a service for website building and hosting. Headquartered in NYC.',
logo: '/assets/logos/logo-squarespace.png',
title: 'Squarespace',
downloads: '835'
}
];
const Page = () => (
<>
<Head>
<title>
Companies | Devias Kit
</title>
</Head>
<Box
component="main"
sx={{
flexGrow: 1,
py: 8
}}
>
<Container maxWidth="xl">
<Stack spacing={3}>
<Stack
direction="row"
justifyContent="space-between"
spacing={4}
>
<Stack spacing={1}>
<Typography variant="h4">
Companies
</Typography>
<Stack
alignItems="center"
direction="row"
spacing={1}
>
<Button
color="inherit"
startIcon={(
<SvgIcon fontSize="small">
<ArrowUpOnSquareIcon />
</SvgIcon>
)}
>
Import
</Button>
<Button
color="inherit"
startIcon={(
<SvgIcon fontSize="small">
<ArrowDownOnSquareIcon />
</SvgIcon>
)}
>
Export
</Button>
</Stack>
</Stack>
<div>
<Button
startIcon={(
<SvgIcon fontSize="small">
<PlusIcon />
</SvgIcon>
)}
variant="contained"
>
Add
</Button>
</div>
</Stack>
<CompaniesSearch />
<Grid
container
spacing={3}
>
{companies.map((company) => (
<Grid
xs={12}
md={6}
lg={4}
key={company.id}
>
<CompanyCard company={company} />
</Grid>
))}
</Grid>
<Box
sx={{
display: 'flex',
justifyContent: 'center'
}}
>
<Pagination
count={3}
size="small"
/>
</Box>
</Stack>
</Container>
</Box>
</>
);
Page.getLayout = (page) => (
<DashboardLayout>
{page}
</DashboardLayout>
);
export default Page;

View File

@ -0,0 +1,290 @@
import { useCallback, useMemo, useState } from 'react';
import Head from 'next/head';
import { subDays, subHours } from 'date-fns';
import ArrowDownOnSquareIcon from '@heroicons/react/24/solid/ArrowDownOnSquareIcon';
import ArrowUpOnSquareIcon from '@heroicons/react/24/solid/ArrowUpOnSquareIcon';
import PlusIcon from '@heroicons/react/24/solid/PlusIcon';
import { Box, Button, Container, Stack, SvgIcon, Typography } from '@mui/material';
import { useSelection } from 'src/hooks/use-selection';
import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout';
import { CustomersTable } from 'src/sections/customer/customers-table';
import { CustomersSearch } from 'src/sections/customer/customers-search';
import { applyPagination } from 'src/utils/apply-pagination';
const now = new Date();
const data = [
{
id: '5e887ac47eed253091be10cb',
address: {
city: 'Cleveland',
country: 'USA',
state: 'Ohio',
street: '2849 Fulton Street'
},
avatar: '/assets/avatars/avatar-carson-darrin.png',
createdAt: subDays(subHours(now, 7), 1).getTime(),
email: 'carson.darrin@devias.io',
name: 'Carson Darrin',
phone: '304-428-3097'
},
{
id: '5e887b209c28ac3dd97f6db5',
address: {
city: 'Atlanta',
country: 'USA',
state: 'Georgia',
street: '1865 Pleasant Hill Road'
},
avatar: '/assets/avatars/avatar-fran-perez.png',
createdAt: subDays(subHours(now, 1), 2).getTime(),
email: 'fran.perez@devias.io',
name: 'Fran Perez',
phone: '712-351-5711'
},
{
id: '5e887b7602bdbc4dbb234b27',
address: {
city: 'North Canton',
country: 'USA',
state: 'Ohio',
street: '4894 Lakeland Park Drive'
},
avatar: '/assets/avatars/avatar-jie-yan-song.png',
createdAt: subDays(subHours(now, 4), 2).getTime(),
email: 'jie.yan.song@devias.io',
name: 'Jie Yan Song',
phone: '770-635-2682'
},
{
id: '5e86809283e28b96d2d38537',
address: {
city: 'Madrid',
country: 'Spain',
name: 'Anika Visser',
street: '4158 Hedge Street'
},
avatar: '/assets/avatars/avatar-anika-visser.png',
createdAt: subDays(subHours(now, 11), 2).getTime(),
email: 'anika.visser@devias.io',
name: 'Anika Visser',
phone: '908-691-3242'
},
{
id: '5e86805e2bafd54f66cc95c3',
address: {
city: 'San Diego',
country: 'USA',
state: 'California',
street: '75247'
},
avatar: '/assets/avatars/avatar-miron-vitold.png',
createdAt: subDays(subHours(now, 7), 3).getTime(),
email: 'miron.vitold@devias.io',
name: 'Miron Vitold',
phone: '972-333-4106'
},
{
id: '5e887a1fbefd7938eea9c981',
address: {
city: 'Berkeley',
country: 'USA',
state: 'California',
street: '317 Angus Road'
},
avatar: '/assets/avatars/avatar-penjani-inyene.png',
createdAt: subDays(subHours(now, 5), 4).getTime(),
email: 'penjani.inyene@devias.io',
name: 'Penjani Inyene',
phone: '858-602-3409'
},
{
id: '5e887d0b3d090c1b8f162003',
address: {
city: 'Carson City',
country: 'USA',
state: 'Nevada',
street: '2188 Armbrester Drive'
},
avatar: '/assets/avatars/avatar-omar-darboe.png',
createdAt: subDays(subHours(now, 15), 4).getTime(),
email: 'omar.darobe@devias.io',
name: 'Omar Darobe',
phone: '415-907-2647'
},
{
id: '5e88792be2d4cfb4bf0971d9',
address: {
city: 'Los Angeles',
country: 'USA',
state: 'California',
street: '1798 Hickory Ridge Drive'
},
avatar: '/assets/avatars/avatar-siegbert-gottfried.png',
createdAt: subDays(subHours(now, 2), 5).getTime(),
email: 'siegbert.gottfried@devias.io',
name: 'Siegbert Gottfried',
phone: '702-661-1654'
},
{
id: '5e8877da9a65442b11551975',
address: {
city: 'Murray',
country: 'USA',
state: 'Utah',
street: '3934 Wildrose Lane'
},
avatar: '/assets/avatars/avatar-iulia-albu.png',
createdAt: subDays(subHours(now, 8), 6).getTime(),
email: 'iulia.albu@devias.io',
name: 'Iulia Albu',
phone: '313-812-8947'
},
{
id: '5e8680e60cba5019c5ca6fda',
address: {
city: 'Salt Lake City',
country: 'USA',
state: 'Utah',
street: '368 Lamberts Branch Road'
},
avatar: '/assets/avatars/avatar-nasimiyu-danai.png',
createdAt: subDays(subHours(now, 1), 9).getTime(),
email: 'nasimiyu.danai@devias.io',
name: 'Nasimiyu Danai',
phone: '801-301-7894'
}
];
const useCustomers = (page, rowsPerPage) => {
return useMemo(
() => {
return applyPagination(data, page, rowsPerPage);
},
[page, rowsPerPage]
);
};
const useCustomerIds = (customers) => {
return useMemo(
() => {
return customers.map((customer) => customer.id);
},
[customers]
);
};
const Page = () => {
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
const customers = useCustomers(page, rowsPerPage);
const customersIds = useCustomerIds(customers);
const customersSelection = useSelection(customersIds);
const handlePageChange = useCallback(
(event, value) => {
setPage(value);
},
[]
);
const handleRowsPerPageChange = useCallback(
(event) => {
setRowsPerPage(event.target.value);
},
[]
);
return (
<>
<Head>
<title>
Customers | Devias Kit
</title>
</Head>
<Box
component="main"
sx={{
flexGrow: 1,
py: 8
}}
>
<Container maxWidth="xl">
<Stack spacing={3}>
<Stack
direction="row"
justifyContent="space-between"
spacing={4}
>
<Stack spacing={1}>
<Typography variant="h4">
Customers
</Typography>
<Stack
alignItems="center"
direction="row"
spacing={1}
>
<Button
color="inherit"
startIcon={(
<SvgIcon fontSize="small">
<ArrowUpOnSquareIcon />
</SvgIcon>
)}
>
Import
</Button>
<Button
color="inherit"
startIcon={(
<SvgIcon fontSize="small">
<ArrowDownOnSquareIcon />
</SvgIcon>
)}
>
Export
</Button>
</Stack>
</Stack>
<div>
<Button
startIcon={(
<SvgIcon fontSize="small">
<PlusIcon />
</SvgIcon>
)}
variant="contained"
>
Add
</Button>
</div>
</Stack>
<CustomersSearch />
<CustomersTable
count={data.length}
items={customers}
onDeselectAll={customersSelection.handleDeselectAll}
onDeselectOne={customersSelection.handleDeselectOne}
onPageChange={handlePageChange}
onRowsPerPageChange={handleRowsPerPageChange}
onSelectAll={customersSelection.handleSelectAll}
onSelectOne={customersSelection.handleSelectOne}
page={page}
rowsPerPage={rowsPerPage}
selected={customersSelection.selected}
/>
</Stack>
</Container>
</Box>
</>
);
};
Page.getLayout = (page) => (
<DashboardLayout>
{page}
</DashboardLayout>
);
export default Page;

232
frontend/src/pages/index.js Normal file
View File

@ -0,0 +1,232 @@
import Head from 'next/head';
import { subDays, subHours } from 'date-fns';
import { Box, Container, Unstable_Grid2 as Grid } from '@mui/material';
import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout';
import { OverviewBudget } from 'src/sections/overview/overview-budget';
import { OverviewLatestOrders } from 'src/sections/overview/overview-latest-orders';
import { OverviewLatestProducts } from 'src/sections/overview/overview-latest-products';
import { OverviewSales } from 'src/sections/overview/overview-sales';
import { OverviewTasksProgress } from 'src/sections/overview/overview-tasks-progress';
import { OverviewTotalCustomers } from 'src/sections/overview/overview-total-customers';
import { OverviewTotalProfit } from 'src/sections/overview/overview-total-profit';
import { OverviewTraffic } from 'src/sections/overview/overview-traffic';
const now = new Date();
const Page = () => (
<>
<Head>
<title>
Overview | Devias Kit
</title>
</Head>
<Box
component="main"
sx={{
flexGrow: 1,
py: 8
}}
>
<Container maxWidth="xl">
<Grid
container
spacing={3}
>
<Grid
xs={12}
sm={6}
lg={3}
>
<OverviewBudget
difference={12}
positive
sx={{ height: '100%' }}
value="$24k"
/>
</Grid>
<Grid
xs={12}
sm={6}
lg={3}
>
<OverviewTotalCustomers
difference={16}
positive={false}
sx={{ height: '100%' }}
value="1.6k"
/>
</Grid>
<Grid
xs={12}
sm={6}
lg={3}
>
<OverviewTasksProgress
sx={{ height: '100%' }}
value={75.5}
/>
</Grid>
<Grid
xs={12}
sm={6}
lg={3}
>
<OverviewTotalProfit
sx={{ height: '100%' }}
value="$15k"
/>
</Grid>
<Grid
xs={12}
lg={8}
>
<OverviewSales
chartSeries={[
{
name: 'This year',
data: [18, 16, 5, 8, 3, 14, 14, 16, 17, 19, 18, 20]
},
{
name: 'Last year',
data: [12, 11, 4, 6, 2, 9, 9, 10, 11, 12, 13, 13]
}
]}
sx={{ height: '100%' }}
/>
</Grid>
<Grid
xs={12}
md={6}
lg={4}
>
<OverviewTraffic
chartSeries={[63, 15, 22]}
labels={['Desktop', 'Tablet', 'Phone']}
sx={{ height: '100%' }}
/>
</Grid>
<Grid
xs={12}
md={6}
lg={4}
>
<OverviewLatestProducts
products={[
{
id: '5ece2c077e39da27658aa8a9',
image: '/assets/products/product-1.png',
name: 'Healthcare Erbology',
updatedAt: subHours(now, 6).getTime()
},
{
id: '5ece2c0d16f70bff2cf86cd8',
image: '/assets/products/product-2.png',
name: 'Makeup Lancome Rouge',
updatedAt: subDays(subHours(now, 8), 2).getTime()
},
{
id: 'b393ce1b09c1254c3a92c827',
image: '/assets/products/product-5.png',
name: 'Skincare Soja CO',
updatedAt: subDays(subHours(now, 1), 1).getTime()
},
{
id: 'a6ede15670da63f49f752c89',
image: '/assets/products/product-6.png',
name: 'Makeup Lipstick',
updatedAt: subDays(subHours(now, 3), 3).getTime()
},
{
id: 'bcad5524fe3a2f8f8620ceda',
image: '/assets/products/product-7.png',
name: 'Healthcare Ritual',
updatedAt: subDays(subHours(now, 5), 6).getTime()
}
]}
sx={{ height: '100%' }}
/>
</Grid>
<Grid
xs={12}
md={12}
lg={8}
>
<OverviewLatestOrders
orders={[
{
id: 'f69f88012978187a6c12897f',
ref: 'DEV1049',
amount: 30.5,
customer: {
name: 'Ekaterina Tankova'
},
createdAt: 1555016400000,
status: 'pending'
},
{
id: '9eaa1c7dd4433f413c308ce2',
ref: 'DEV1048',
amount: 25.1,
customer: {
name: 'Cao Yu'
},
createdAt: 1555016400000,
status: 'delivered'
},
{
id: '01a5230c811bd04996ce7c13',
ref: 'DEV1047',
amount: 10.99,
customer: {
name: 'Alexa Richardson'
},
createdAt: 1554930000000,
status: 'refunded'
},
{
id: '1f4e1bd0a87cea23cdb83d18',
ref: 'DEV1046',
amount: 96.43,
customer: {
name: 'Anje Keizer'
},
createdAt: 1554757200000,
status: 'pending'
},
{
id: '9f974f239d29ede969367103',
ref: 'DEV1045',
amount: 32.54,
customer: {
name: 'Clarke Gillebert'
},
createdAt: 1554670800000,
status: 'delivered'
},
{
id: 'ffc83c1560ec2f66a1c05596',
ref: 'DEV1044',
amount: 16.76,
customer: {
name: 'Adam Denisov'
},
createdAt: 1554670800000,
status: 'delivered'
}
]}
sx={{ height: '100%' }}
/>
</Grid>
</Grid>
</Container>
</Box>
</>
);
Page.getLayout = (page) => (
<DashboardLayout>
{page}
</DashboardLayout>
);
export default Page;

View File

@ -0,0 +1,40 @@
import Head from 'next/head';
import { Box, Container, Stack, Typography } from '@mui/material';
import { SettingsNotifications } from 'src/sections/settings/settings-notifications';
import { SettingsPassword } from 'src/sections/settings/settings-password';
import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout';
const Page = () => (
<>
<Head>
<title>
Settings | Devias Kit
</title>
</Head>
<Box
component="main"
sx={{
flexGrow: 1,
py: 8
}}
>
<Container maxWidth="lg">
<Stack spacing={3}>
<Typography variant="h4">
Settings
</Typography>
<SettingsNotifications />
<SettingsPassword />
</Stack>
</Container>
</Box>
</>
);
Page.getLayout = (page) => (
<DashboardLayout>
{page}
</DashboardLayout>
);
export default Page;

View File

@ -0,0 +1,179 @@
import { useCallback, useState } from 'react';
import {
Box,
Button,
Card,
CardActions,
CardContent,
CardHeader,
Divider,
TextField,
Unstable_Grid2 as Grid
} from '@mui/material';
const states = [
{
value: 'alabama',
label: 'Alabama'
},
{
value: 'new-york',
label: 'New York'
},
{
value: 'san-francisco',
label: 'San Francisco'
},
{
value: 'los-angeles',
label: 'Los Angeles'
}
];
export const AccountProfileDetails = () => {
const [values, setValues] = useState({
firstName: 'Anika',
lastName: 'Visser',
email: 'demo@devias.io',
phone: '',
state: 'los-angeles',
country: 'USA'
});
const handleChange = useCallback(
(event) => {
setValues((prevState) => ({
...prevState,
[event.target.name]: event.target.value
}));
},
[]
);
const handleSubmit = useCallback(
(event) => {
event.preventDefault();
},
[]
);
return (
<form
autoComplete="off"
noValidate
onSubmit={handleSubmit}
>
<Card>
<CardHeader
subheader="The information can be edited"
title="Profile"
/>
<CardContent sx={{ pt: 0 }}>
<Box sx={{ m: -1.5 }}>
<Grid
container
spacing={3}
>
<Grid
xs={12}
md={6}
>
<TextField
fullWidth
helperText="Please specify the first name"
label="First name"
name="firstName"
onChange={handleChange}
required
value={values.firstName}
/>
</Grid>
<Grid
xs={12}
md={6}
>
<TextField
fullWidth
label="Last name"
name="lastName"
onChange={handleChange}
required
value={values.lastName}
/>
</Grid>
<Grid
xs={12}
md={6}
>
<TextField
fullWidth
label="Email Address"
name="email"
onChange={handleChange}
required
value={values.email}
/>
</Grid>
<Grid
xs={12}
md={6}
>
<TextField
fullWidth
label="Phone Number"
name="phone"
onChange={handleChange}
type="number"
value={values.phone}
/>
</Grid>
<Grid
xs={12}
md={6}
>
<TextField
fullWidth
label="Country"
name="country"
onChange={handleChange}
required
value={values.country}
/>
</Grid>
<Grid
xs={12}
md={6}
>
<TextField
fullWidth
label="Select State"
name="state"
onChange={handleChange}
required
select
SelectProps={{ native: true }}
value={values.state}
>
{states.map((option) => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
))}
</TextField>
</Grid>
</Grid>
</Box>
</CardContent>
<Divider />
<CardActions sx={{ justifyContent: 'flex-end' }}>
<Button variant="contained">
Save details
</Button>
</CardActions>
</Card>
</form>
);
};

View File

@ -0,0 +1,69 @@
import {
Avatar,
Box,
Button,
Card,
CardActions,
CardContent,
Divider,
Typography
} from '@mui/material';
const user = {
avatar: '/assets/avatars/avatar-anika-visser.png',
city: 'Los Angeles',
country: 'USA',
jobTitle: 'Senior Developer',
name: 'Anika Visser',
timezone: 'GTM-7'
};
export const AccountProfile = () => (
<Card>
<CardContent>
<Box
sx={{
alignItems: 'center',
display: 'flex',
flexDirection: 'column'
}}
>
<Avatar
src={user.avatar}
sx={{
height: 80,
mb: 2,
width: 80
}}
/>
<Typography
gutterBottom
variant="h5"
>
{user.name}
</Typography>
<Typography
color="text.secondary"
variant="body2"
>
{user.city} {user.country}
</Typography>
<Typography
color="text.secondary"
variant="body2"
>
{user.timezone}
</Typography>
</Box>
</CardContent>
<Divider />
<CardActions>
<Button
fullWidth
variant="text"
>
Upload picture
</Button>
</CardActions>
</Card>
);

View File

@ -0,0 +1,23 @@
import MagnifyingGlassIcon from '@heroicons/react/24/solid/MagnifyingGlassIcon';
import { Card, InputAdornment, OutlinedInput, SvgIcon } from '@mui/material';
export const CompaniesSearch = () => (
<Card sx={{ p: 2 }}>
<OutlinedInput
defaultValue=""
fullWidth
placeholder="Search company"
startAdornment={(
<InputAdornment position="start">
<SvgIcon
color="action"
fontSize="small"
>
<MagnifyingGlassIcon />
</SvgIcon>
</InputAdornment>
)}
sx={{ maxWidth: 500 }}
/>
</Card>
);

View File

@ -0,0 +1,98 @@
import PropTypes from 'prop-types';
import ArrowDownOnSquareIcon from '@heroicons/react/24/solid/ArrowDownOnSquareIcon';
import ClockIcon from '@heroicons/react/24/solid/ClockIcon';
import { Avatar, Box, Card, CardContent, Divider, Stack, SvgIcon, Typography } from '@mui/material';
export const CompanyCard = (props) => {
const { company } = props;
return (
<Card
sx={{
display: 'flex',
flexDirection: 'column',
height: '100%'
}}
>
<CardContent>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
pb: 3
}}
>
<Avatar
src={company.logo}
variant="square"
/>
</Box>
<Typography
align="center"
gutterBottom
variant="h5"
>
{company.title}
</Typography>
<Typography
align="center"
variant="body1"
>
{company.description}
</Typography>
</CardContent>
<Box sx={{ flexGrow: 1 }} />
<Divider />
<Stack
alignItems="center"
direction="row"
justifyContent="space-between"
spacing={2}
sx={{ p: 2 }}
>
<Stack
alignItems="center"
direction="row"
spacing={1}
>
<SvgIcon
color="action"
fontSize="small"
>
<ClockIcon />
</SvgIcon>
<Typography
color="text.secondary"
display="inline"
variant="body2"
>
Updated 2hr ago
</Typography>
</Stack>
<Stack
alignItems="center"
direction="row"
spacing={1}
>
<SvgIcon
color="action"
fontSize="small"
>
<ArrowDownOnSquareIcon />
</SvgIcon>
<Typography
color="text.secondary"
display="inline"
variant="body2"
>
{company.downloads} Downloads
</Typography>
</Stack>
</Stack>
</Card>
);
};
CompanyCard.propTypes = {
company: PropTypes.object.isRequired
};

View File

@ -0,0 +1,23 @@
import MagnifyingGlassIcon from '@heroicons/react/24/solid/MagnifyingGlassIcon';
import { Card, InputAdornment, OutlinedInput, SvgIcon } from '@mui/material';
export const CustomersSearch = () => (
<Card sx={{ p: 2 }}>
<OutlinedInput
defaultValue=""
fullWidth
placeholder="Search customer"
startAdornment={(
<InputAdornment position="start">
<SvgIcon
color="action"
fontSize="small"
>
<MagnifyingGlassIcon />
</SvgIcon>
</InputAdornment>
)}
sx={{ maxWidth: 500 }}
/>
</Card>
);

View File

@ -0,0 +1,156 @@
import PropTypes from 'prop-types';
import { format } from 'date-fns';
import {
Avatar,
Box,
Card,
Checkbox,
Stack,
Table,
TableBody,
TableCell,
TableHead,
TablePagination,
TableRow,
Typography
} from '@mui/material';
import { Scrollbar } from 'src/components/scrollbar';
import { getInitials } from 'src/utils/get-initials';
export const CustomersTable = (props) => {
const {
count = 0,
items = [],
onDeselectAll,
onDeselectOne,
onPageChange = () => {},
onRowsPerPageChange,
onSelectAll,
onSelectOne,
page = 0,
rowsPerPage = 0,
selected = []
} = props;
const selectedSome = (selected.length > 0) && (selected.length < items.length);
const selectedAll = (items.length > 0) && (selected.length === items.length);
return (
<Card>
<Scrollbar>
<Box sx={{ minWidth: 800 }}>
<Table>
<TableHead>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
checked={selectedAll}
indeterminate={selectedSome}
onChange={(event) => {
if (event.target.checked) {
onSelectAll?.();
} else {
onDeselectAll?.();
}
}}
/>
</TableCell>
<TableCell>
Name
</TableCell>
<TableCell>
Email
</TableCell>
<TableCell>
Location
</TableCell>
<TableCell>
Phone
</TableCell>
<TableCell>
Signed Up
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((customer) => {
const isSelected = selected.includes(customer.id);
const createdAt = format(customer.createdAt, 'dd/MM/yyyy');
return (
<TableRow
hover
key={customer.id}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
onChange={(event) => {
if (event.target.checked) {
onSelectOne?.(customer.id);
} else {
onDeselectOne?.(customer.id);
}
}}
/>
</TableCell>
<TableCell>
<Stack
alignItems="center"
direction="row"
spacing={2}
>
<Avatar src={customer.avatar}>
{getInitials(customer.name)}
</Avatar>
<Typography variant="subtitle2">
{customer.name}
</Typography>
</Stack>
</TableCell>
<TableCell>
{customer.email}
</TableCell>
<TableCell>
{customer.address.city}, {customer.address.state}, {customer.address.country}
</TableCell>
<TableCell>
{customer.phone}
</TableCell>
<TableCell>
{createdAt}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Box>
</Scrollbar>
<TablePagination
component="div"
count={count}
onPageChange={onPageChange}
onRowsPerPageChange={onRowsPerPageChange}
page={page}
rowsPerPage={rowsPerPage}
rowsPerPageOptions={[5, 10, 25]}
/>
</Card>
);
};
CustomersTable.propTypes = {
count: PropTypes.number,
items: PropTypes.array,
onDeselectAll: PropTypes.func,
onDeselectOne: PropTypes.func,
onPageChange: PropTypes.func,
onRowsPerPageChange: PropTypes.func,
onSelectAll: PropTypes.func,
onSelectOne: PropTypes.func,
page: PropTypes.number,
rowsPerPage: PropTypes.number,
selected: PropTypes.array
};

View File

@ -0,0 +1,85 @@
import PropTypes from 'prop-types';
import ArrowDownIcon from '@heroicons/react/24/solid/ArrowDownIcon';
import ArrowUpIcon from '@heroicons/react/24/solid/ArrowUpIcon';
import CurrencyDollarIcon from '@heroicons/react/24/solid/CurrencyDollarIcon';
import { Avatar, Card, CardContent, Stack, SvgIcon, Typography } from '@mui/material';
export const OverviewBudget = (props) => {
const { difference, positive = false, sx, value } = props;
return (
<Card sx={sx}>
<CardContent>
<Stack
alignItems="flex-start"
direction="row"
justifyContent="space-between"
spacing={3}
>
<Stack spacing={1}>
<Typography
color="text.secondary"
variant="overline"
>
Budget
</Typography>
<Typography variant="h4">
{value}
</Typography>
</Stack>
<Avatar
sx={{
backgroundColor: 'error.main',
height: 56,
width: 56
}}
>
<SvgIcon>
<CurrencyDollarIcon />
</SvgIcon>
</Avatar>
</Stack>
{difference && (
<Stack
alignItems="center"
direction="row"
spacing={2}
sx={{ mt: 2 }}
>
<Stack
alignItems="center"
direction="row"
spacing={0.5}
>
<SvgIcon
color={positive ? 'success' : 'error'}
fontSize="small"
>
{positive ? <ArrowUpIcon /> : <ArrowDownIcon />}
</SvgIcon>
<Typography
color={positive ? 'success.main' : 'error.main'}
variant="body2"
>
{difference}%
</Typography>
</Stack>
<Typography
color="text.secondary"
variant="caption"
>
Since last month
</Typography>
</Stack>
)}
</CardContent>
</Card>
);
};
OverviewBudget.prototypes = {
difference: PropTypes.number,
positive: PropTypes.bool,
sx: PropTypes.object,
value: PropTypes.string.isRequired
};

View File

@ -0,0 +1,104 @@
import { format } from 'date-fns';
import PropTypes from 'prop-types';
import ArrowRightIcon from '@heroicons/react/24/solid/ArrowRightIcon';
import {
Box,
Button,
Card,
CardActions,
CardHeader,
Divider,
SvgIcon,
Table,
TableBody,
TableCell,
TableHead,
TableRow
} from '@mui/material';
import { Scrollbar } from 'src/components/scrollbar';
import { SeverityPill } from 'src/components/severity-pill';
const statusMap = {
pending: 'warning',
delivered: 'success',
refunded: 'error'
};
export const OverviewLatestOrders = (props) => {
const { orders = [], sx } = props;
return (
<Card sx={sx}>
<CardHeader title="Latest Orders" />
<Scrollbar sx={{ flexGrow: 1 }}>
<Box sx={{ minWidth: 800 }}>
<Table>
<TableHead>
<TableRow>
<TableCell>
Order
</TableCell>
<TableCell>
Customer
</TableCell>
<TableCell sortDirection="desc">
Date
</TableCell>
<TableCell>
Status
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{orders.map((order) => {
const createdAt = format(order.createdAt, 'dd/MM/yyyy');
return (
<TableRow
hover
key={order.id}
>
<TableCell>
{order.ref}
</TableCell>
<TableCell>
{order.customer.name}
</TableCell>
<TableCell>
{createdAt}
</TableCell>
<TableCell>
<SeverityPill color={statusMap[order.status]}>
{order.status}
</SeverityPill>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Box>
</Scrollbar>
<Divider />
<CardActions sx={{ justifyContent: 'flex-end' }}>
<Button
color="inherit"
endIcon={(
<SvgIcon fontSize="small">
<ArrowRightIcon />
</SvgIcon>
)}
size="small"
variant="text"
>
View all
</Button>
</CardActions>
</Card>
);
};
OverviewLatestOrders.prototype = {
orders: PropTypes.array,
sx: PropTypes.object
};

View File

@ -0,0 +1,99 @@
import { formatDistanceToNow } from 'date-fns';
import PropTypes from 'prop-types';
import ArrowRightIcon from '@heroicons/react/24/solid/ArrowRightIcon';
import EllipsisVerticalIcon from '@heroicons/react/24/solid/EllipsisVerticalIcon';
import {
Box,
Button,
Card,
CardActions,
CardHeader,
Divider,
IconButton,
List,
ListItem,
ListItemAvatar,
ListItemText,
SvgIcon
} from '@mui/material';
export const OverviewLatestProducts = (props) => {
const { products = [], sx } = props;
return (
<Card sx={sx}>
<CardHeader title="Latest Products" />
<List>
{products.map((product, index) => {
const hasDivider = index < products.length - 1;
const ago = formatDistanceToNow(product.updatedAt);
return (
<ListItem
divider={hasDivider}
key={product.id}
>
<ListItemAvatar>
{
product.image
? (
<Box
component="img"
src={product.image}
sx={{
borderRadius: 1,
height: 48,
width: 48
}}
/>
)
: (
<Box
sx={{
borderRadius: 1,
backgroundColor: 'neutral.200',
height: 48,
width: 48
}}
/>
)
}
</ListItemAvatar>
<ListItemText
primary={product.name}
primaryTypographyProps={{ variant: 'subtitle1' }}
secondary={`Updated ${ago} ago`}
secondaryTypographyProps={{ variant: 'body2' }}
/>
<IconButton edge="end">
<SvgIcon>
<EllipsisVerticalIcon />
</SvgIcon>
</IconButton>
</ListItem>
);
})}
</List>
<Divider />
<CardActions sx={{ justifyContent: 'flex-end' }}>
<Button
color="inherit"
endIcon={(
<SvgIcon fontSize="small">
<ArrowRightIcon />
</SvgIcon>
)}
size="small"
variant="text"
>
View all
</Button>
</CardActions>
</Card>
);
};
OverviewLatestProducts.propTypes = {
products: PropTypes.array,
sx: PropTypes.object
};

Some files were not shown because too many files have changed in this diff Show More