diff --git a/apps/app/pages/about.js b/apps/app/pages/about.js new file mode 100644 index 000000000..60ff68a66 --- /dev/null +++ b/apps/app/pages/about.js @@ -0,0 +1,8 @@ +import Page from '@devlaunchers/website/src/pages/about'; +import App from '@devlaunchers/website/src/pages/_app'; +//export { getStaticProps } from '@devlaunchers/website/src/pages/index'; + +///////////////////////////////////////// + +import { constructAppPage } from '../utils/routingTools.js'; +export default constructAppPage(App, Page); diff --git a/apps/app/pages/collaborate.js b/apps/app/pages/collaborate.js new file mode 100644 index 000000000..6635d49e7 --- /dev/null +++ b/apps/app/pages/collaborate.js @@ -0,0 +1,8 @@ +import Page from '@devlaunchers/website/src/pages/collaborate.js'; +import App from '@devlaunchers/website/src/pages/_app'; +//export { getStaticProps } from '@devlaunchers/website/src/pages/index'; + +///////////////////////////////////////// + +import { constructAppPage } from '../utils/routingTools.js'; +export default constructAppPage(App, Page); diff --git a/apps/app/pages/resources.js b/apps/app/pages/resources.js new file mode 100644 index 000000000..6c5de73aa --- /dev/null +++ b/apps/app/pages/resources.js @@ -0,0 +1,8 @@ +import Page from '@devlaunchers/website/src/pages/resources'; +import App from '@devlaunchers/website/src/pages/_app'; +//export { getStaticProps } from '@devlaunchers/website/src/pages/index'; + +///////////////////////////////////////// + +import { constructAppPage } from '../utils/routingTools.js'; +export default constructAppPage(App, Page); diff --git a/apps/app/public/background-shape.png b/apps/app/public/background-shape.png new file mode 100644 index 000000000..b88f56665 Binary files /dev/null and b/apps/app/public/background-shape.png differ diff --git a/apps/app/public/dev_image (1).png b/apps/app/public/dev_image (1).png new file mode 100644 index 000000000..403028575 Binary files /dev/null and b/apps/app/public/dev_image (1).png differ diff --git a/apps/app/public/dev_image (2).png b/apps/app/public/dev_image (2).png new file mode 100644 index 000000000..3cf0535be Binary files /dev/null and b/apps/app/public/dev_image (2).png differ diff --git a/apps/app/public/dev_image (3).png b/apps/app/public/dev_image (3).png new file mode 100644 index 000000000..edb2141cb Binary files /dev/null and b/apps/app/public/dev_image (3).png differ diff --git a/apps/app/public/dev_image (4).png b/apps/app/public/dev_image (4).png new file mode 100644 index 000000000..f8a0f0f7b Binary files /dev/null and b/apps/app/public/dev_image (4).png differ diff --git a/apps/app/public/dev_image (5).png b/apps/app/public/dev_image (5).png new file mode 100644 index 000000000..2463f0a12 Binary files /dev/null and b/apps/app/public/dev_image (5).png differ diff --git a/apps/app/public/dev_image.png b/apps/app/public/dev_image.png new file mode 100644 index 000000000..092d45180 Binary files /dev/null and b/apps/app/public/dev_image.png differ diff --git a/apps/app/public/figma.svg b/apps/app/public/figma.svg new file mode 100644 index 000000000..825dedaac --- /dev/null +++ b/apps/app/public/figma.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/app/public/github.svg b/apps/app/public/github.svg new file mode 100644 index 000000000..a824373a5 --- /dev/null +++ b/apps/app/public/github.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/app/public/grad-01.png b/apps/app/public/grad-01.png new file mode 100644 index 000000000..6329e2a48 Binary files /dev/null and b/apps/app/public/grad-01.png differ diff --git a/apps/app/public/grad-02.png b/apps/app/public/grad-02.png new file mode 100644 index 000000000..1ad91723d Binary files /dev/null and b/apps/app/public/grad-02.png differ diff --git a/apps/app/public/images/home/build.png b/apps/app/public/images/home/build.png new file mode 100644 index 000000000..06f498087 Binary files /dev/null and b/apps/app/public/images/home/build.png differ diff --git a/apps/app/public/images/home/google.png b/apps/app/public/images/home/google.png new file mode 100644 index 000000000..929c906e8 Binary files /dev/null and b/apps/app/public/images/home/google.png differ diff --git a/apps/app/public/images/home/ideas.png b/apps/app/public/images/home/ideas.png new file mode 100644 index 000000000..907a11104 Binary files /dev/null and b/apps/app/public/images/home/ideas.png differ diff --git a/apps/app/public/images/home/krafties.png b/apps/app/public/images/home/krafties.png new file mode 100644 index 000000000..60f171edc Binary files /dev/null and b/apps/app/public/images/home/krafties.png differ diff --git a/apps/app/public/images/home/lead.png b/apps/app/public/images/home/lead.png new file mode 100644 index 000000000..e0b2673bd Binary files /dev/null and b/apps/app/public/images/home/lead.png differ diff --git a/apps/app/public/images/home/learning.png b/apps/app/public/images/home/learning.png new file mode 100644 index 000000000..0e688c6fc Binary files /dev/null and b/apps/app/public/images/home/learning.png differ diff --git a/apps/app/public/images/home/logo-figma.png b/apps/app/public/images/home/logo-figma.png new file mode 100644 index 000000000..ce346634f Binary files /dev/null and b/apps/app/public/images/home/logo-figma.png differ diff --git a/apps/app/public/images/home/logo-github.png b/apps/app/public/images/home/logo-github.png new file mode 100644 index 000000000..089b8c85a Binary files /dev/null and b/apps/app/public/images/home/logo-github.png differ diff --git a/apps/app/public/images/home/logo-react.png b/apps/app/public/images/home/logo-react.png new file mode 100644 index 000000000..4f6b4873b Binary files /dev/null and b/apps/app/public/images/home/logo-react.png differ diff --git a/apps/app/public/images/home/logo-shadcn.png b/apps/app/public/images/home/logo-shadcn.png new file mode 100644 index 000000000..face53ea7 Binary files /dev/null and b/apps/app/public/images/home/logo-shadcn.png differ diff --git a/apps/app/public/images/home/logo-tailwind.png b/apps/app/public/images/home/logo-tailwind.png new file mode 100644 index 000000000..f5eaf2e90 Binary files /dev/null and b/apps/app/public/images/home/logo-tailwind.png differ diff --git a/apps/app/public/images/home/logo-typescript.png b/apps/app/public/images/home/logo-typescript.png new file mode 100644 index 000000000..2734e0235 Binary files /dev/null and b/apps/app/public/images/home/logo-typescript.png differ diff --git a/apps/app/public/images/home/map.png b/apps/app/public/images/home/map.png new file mode 100644 index 000000000..e710cc3b0 Binary files /dev/null and b/apps/app/public/images/home/map.png differ diff --git a/apps/app/public/images/home/microsoft.png b/apps/app/public/images/home/microsoft.png new file mode 100644 index 000000000..523d89d5f Binary files /dev/null and b/apps/app/public/images/home/microsoft.png differ diff --git a/apps/app/public/images/home/partner.png b/apps/app/public/images/home/partner.png new file mode 100644 index 000000000..c6d5cd64a Binary files /dev/null and b/apps/app/public/images/home/partner.png differ diff --git a/apps/app/public/react.svg b/apps/app/public/react.svg new file mode 100644 index 000000000..f4d477329 --- /dev/null +++ b/apps/app/public/react.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/app/public/shadcn.svg b/apps/app/public/shadcn.svg new file mode 100644 index 000000000..a509b4ad4 --- /dev/null +++ b/apps/app/public/shadcn.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/app/public/tailwind.svg b/apps/app/public/tailwind.svg new file mode 100644 index 000000000..c1e7cc0d8 --- /dev/null +++ b/apps/app/public/tailwind.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/app/public/typescript.svg b/apps/app/public/typescript.svg new file mode 100644 index 000000000..c6257ce5d --- /dev/null +++ b/apps/app/public/typescript.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/ideaspace/src/components/common/IdeaForm/IdeaForm.js b/apps/ideaspace/src/components/common/IdeaForm/IdeaForm.js index 07ba72375..54b3ad982 100644 --- a/apps/ideaspace/src/components/common/IdeaForm/IdeaForm.js +++ b/apps/ideaspace/src/components/common/IdeaForm/IdeaForm.js @@ -9,180 +9,484 @@ import Dropdown from '@devlaunchers/components/components/organisms/Dropdown'; import useResponsive from '@devlaunchers/components/src/hooks/useResponsive'; import Checkbox from '@devlaunchers/components/src/components/Checkbox/Checkbox'; import Link from 'next/link'; -Link; - -const IdeaForm = ( - { - initialValues, - SignupSchema, - submitHandler, - unsavedHandler, - formButton, - sending, - clickHandler, - }, - props -) => { - const { errors } = props; +import { + TextAreaWrapper, + FieldWrapper, + FieldLabel, + RequiredAsterisk, + StyledTextArea, + CharacterCounter, + StyledInput, + GroupWrapper, + GroupLabel, + RadioButton, + RadioInput, + RadioLabel, + ErrorText, + SuccessText, +} from '../../modules/SubmissionForm/StyledSubmissionForm'; + +import SuccessAlert from '../SubmissionAlert/SuccessAlert.js'; + +const compareValuesToInitial = (values, initialValues) => { + const name = Object.keys(values); + for (let i = 0; i < name.length; i++) { + if (values[name[i]] !== initialValues[name[i]]) { + return true; + } + } + return false; +}; + +const autoSaveLocalStorage = (values) => { + localStorage.setItem('ideaFormData', JSON.stringify(values)); +}; + +const loadFromLocalStorage = () => { + const savedData = localStorage.getItem('ideaFormData'); + return savedData ? JSON.parse(savedData) : null; +}; + +const clearLocalStorage = () => { + localStorage.removeItem('ideaFormData'); + localStorage.removeItem('involveLevel'); +}; + +const AutoSubmitToken = ({ setDisabling, unsavedHandler, initialValues }) => { + const { values } = useFormikContext(); + const [previousValues, setPreviousValues] = useState(values); + React.useEffect(() => { + autoSaveLocalStorage(values); + if (compareValuesToInitial(values, initialValues)) { + unsavedHandler(true); + setDisabling(false); + } else if (JSON.stringify(values) !== JSON.stringify(previousValues)) { + unsavedHandler(false); + setDisabling(true); + setPreviousValues(values); + } + }, [values, previousValues, initialValues, setDisabling, unsavedHandler]); + return null; +}; + +const IdeaForm = ({ + initialValues, + SignupSchema, + submitHandler, + unsavedHandler, + formButton, + sending, + clickHandler, +}) => { + const [focusedField, setFocusedField] = useState(null); const [disabling, setDisabling] = React.useState(true); const { isMobile } = useResponsive(); + const [successMessageVisible, setSuccessMessageVisible] = useState(false); - const compareValuesToInitial = (values) => { - const name = Object.keys(values); - for (let i = 0; i < name.length; i++) { - if (values[name[i]] !== initialValues[name[i]]) { - return true; - } - } - return false; + const isFieldCompleted = (value, error, fieldName) => { + return value && !error && focusedField !== fieldName; }; - const autoSaveLocalStorage = (values) => { - localStorage.setItem('ideaFormData', JSON.stringify(values)); + const handleFocus = (fieldName) => { + setFocusedField(fieldName); }; - const loadFromLocalStorage = () => { - const savedData = localStorage.getItem('ideaFormData'); - return savedData ? JSON.parse(savedData) : null; - }; + const savedData = loadFromLocalStorage(); + const newInitialValues = { ...initialValues, ...savedData }; - const clearLocalStorage = () => { + React.useEffect(() => { localStorage.removeItem('ideaFormData'); + return () => { + localStorage.removeItem('ideaFormData'); + }; + }, []); + + const handleSubmit = (values, actions) => { + submitHandler(values, actions); + setSuccessMessageVisible(true); + actions.resetForm({ values: initialValues }); + clearLocalStorage(); }; - const AutoSubmitToken = () => { - const { values, submitForm } = useFormikContext(); - const [previousValues, setPreviousValues] = useState(values); - React.useEffect(() => { - autoSaveLocalStorage(values); - if (compareValuesToInitial(values)) { - unsavedHandler(true); - setDisabling(false); - } else if (JSON.stringify(values) !== JSON.stringify(previousValues)) { - unsavedHandler(false); - setDisabling(true); - setPreviousValues(values); + const renderFieldMessage = ( + fieldName, + value, + touched, + error, + maxLength, + isRequired = true + ) => { + if (focusedField === fieldName) { + return ( + + {value?.length || 0}/{maxLength} characters + + ); + } else { + if (isRequired) { + if (error && touched) { + return {error}; + } + } + if (value && !error) { + return Completed!; } - }, [values, previousValues]); + } return null; }; - const savedData = loadFromLocalStorage(); - const newInitialValues = { ...initialValues, ...savedData }; + const scrollToError = (errors) => { + const firstError = Object.keys(errors)[0]; + if (firstError) { + const errorElement = document.querySelector( + `[data-field="${firstError}"]` + ); + + if (errorElement) { + setTimeout(() => { + errorElement.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }); + }, 100); + } + } + }; return ( - + { - submitHandler(values, actions); - clearLocalStorage(); // Clear localStorage on form submission - actions.resetForm({ values: initialValues }); - }} + onSubmit={handleSubmit} enableReinitialize + validateOnMount={true} > - {({ errors, setFieldValue, touched }) => ( + {({ + values, + setFieldValue, + errors, + touched, + handleBlur, + submitForm, + isValid, + setFieldTouched, + validateForm, + }) => (
- + Idea Info -
+
- - - - - - - - - - - - - - - - - - - - + + Idea Name + * + + + + setFieldValue('ideaName', e.target.value.slice(0, 80)) + } + maxLength={80} + onFocus={() => setFocusedField('ideaName')} + onBlur={(e) => { + handleBlur(e); + setFocusedField(null); + }} + /> + + {renderFieldMessage( + 'ideaName', + values.ideaName, + touched.ideaName, + errors.ideaName, + 80 + )} + + + {/* Description Field */} + + + Describe your idea: + * + + + + setFieldValue( + 'description', + e.target.value.slice(0, 1000) + ) + } + maxLength={1000} + onFocus={() => setFocusedField('description')} + onBlur={(e) => { + handleBlur(e); + setFocusedField(null); + }} + /> + + {renderFieldMessage( + 'description', + values.description, + touched.description, + errors.description, + 1000 + )} + + + {/* Experience Field */} + + + What experience do you have with this idea? + * + + + + setFieldValue( + 'experience', + e.target.value.slice(0, 1000) + ) + } + maxLength={1000} + onFocus={() => setFocusedField('experience')} + onBlur={(e) => { + handleBlur(e); + setFocusedField(null); + }} + /> + + {renderFieldMessage( + 'experience', + values.experience, + touched.experience, + errors.experience, + 1000 + )} + + + {/* Target Audience Field */} + + + Describe your expected users: + * + + + { + const text = e.target.value.slice(0, 1000); + setFieldValue('targetAudience', text); + }} + maxLength={1000} + onFocus={() => setFocusedField('targetAudience')} + onBlur={(e) => { + handleBlur(e); + setFocusedField(null); + }} + /> + + {renderFieldMessage( + 'targetAudience', + values.targetAudience, + touched.targetAudience, + errors.targetAudience, + 1000 + )} + + + {/* Features Field - Required */} + + + What Features Would Your Product Have? + * + + + { + const text = e.target.value.slice(0, 1000); + setFieldValue('features', text); + }} + maxLength={1000} + onFocus={() => handleFocus('features')} + onBlur={(e) => { + handleBlur(e); + setFocusedField(null); + }} + /> + + {renderFieldMessage( + 'features', + values.features, + touched.features, + errors.features, + 1000 + )} + + + {/* Extra Info Field - Optional */} + + + Anything else you would like to share to support your idea?{' '} + + (Optional) + + + + { + const text = e.target.value.slice(0, 1000); + setFieldValue('extraInfo', text); + }} + maxLength={1000} + onFocus={() => handleFocus('extraInfo')} + onBlur={(e) => { + handleBlur(e); + setFocusedField(null); + }} + /> + + {renderFieldMessage( + 'extraInfo', + values.extraInfo, + touched.extraInfo, + errors.extraInfo, + 1000, + false + )} + + + {/* Tagline Field - Optional */} + + + What's a catchy tagline for your idea that sums up your + value and purpose? (Optional) + + + { + const text = e.target.value.slice(0, 80); + setFieldValue('tagline', text); + }} + maxLength={80} + onFocus={() => handleFocus('tagline')} + onBlur={(e) => { + handleBlur(e); + setFocusedField(null); + }} + /> + + {renderFieldMessage( + 'tagline', + values.tagline, + touched.tagline, + errors.tagline, + 80, + false + )} + + + {/* Involvement Level - Required */} + + What level of involvement would you like to have after this - submission? * + submission? + * submit_image - - - - setFieldValue('involveLevel', 'highly') + + + + + setFieldValue('involveLevel', e.target.value) } - label='I want to "own" this idea from beginning to end' + onBlur={handleBlur} /> - - setFieldValue('involveLevel', 'medium') + + Level 1 - Highly +
I want to "own" this idea from beginning to end +
+
+ + + + setFieldValue('involveLevel', e.target.value) } - label='I want to "own" this idea only after it is approved as a project' + onBlur={handleBlur} /> - - setFieldValue('involveLevel', 'minimum') + + Level 2 - Medium +
I want to "own" this idea only after it is approved + as a project +
+
+ + + + setFieldValue('involveLevel', e.target.value) } - label='I want to "own" this idea during workshopping only' + onBlur={handleBlur} /> - - setFieldValue('involveLevel', 'none') + + Level 3 - Minimum +
I want to "own" this idea during workshopping only +
+
+ + + + setFieldValue('involveLevel', e.target.value) } - label="I don't want to be involved after submitting" + onBlur={handleBlur} /> -
- - {' '} - {errors.involveLevel} - -
+ + Level 4 - None +
I don't want to be involved after submitting +
+ + + {touched.involveLevel && errors.involveLevel && ( + {errors.involveLevel} + )} + After submitting your idea, it will be posted in the @@ -259,16 +602,53 @@ const IdeaForm = ( {formButton == 'submit' ? ( - + { + e.preventDefault(); + + const fields = [ + 'ideaName', + 'description', + 'experience', + 'targetAudience', + 'features', + 'involveLevel', + ]; + fields.forEach((field) => setFieldTouched(field, true)); + + const validationErrors = await validateForm(); + if (Object.keys(validationErrors).length > 0) { + scrollToError(validationErrors); + return; + } + + submitForm(); + }} + /> ) : ( { + e.preventDefault(); + try { + await submitForm(); + if (Object.keys(errors).length > 0) { + scrollToError(errors); + } + } catch (error) { + console.error('Form submission error:', error); + } + }} /> )}
+ {successMessageVisible && ( + setSuccessMessageVisible(false)} /> + )} )}
diff --git a/apps/ideaspace/src/components/common/IdeaForm/SubmissionButton.js b/apps/ideaspace/src/components/common/IdeaForm/SubmissionButton.js index fd4ab4790..c0a2e274b 100644 --- a/apps/ideaspace/src/components/common/IdeaForm/SubmissionButton.js +++ b/apps/ideaspace/src/components/common/IdeaForm/SubmissionButton.js @@ -1,17 +1,18 @@ import React from 'react'; import { atoms } from '@devlaunchers/components/src/components'; -const SubmissionButton = ({sending}) => { +const SubmissionButton = ({ sending, onClick }) => { + return ( + + {' '} + {sending === true ? 'Wait' : 'Post Idea'}{' '} + + ); +}; - return ( - - {' '}{sending === true ? 'Wait' : 'Submit'}{' '} - - ) -} - -export default SubmissionButton; \ No newline at end of file +export default SubmissionButton; diff --git a/apps/ideaspace/src/components/common/SubmissionAlert/SuccessAlert.js b/apps/ideaspace/src/components/common/SubmissionAlert/SuccessAlert.js new file mode 100644 index 000000000..4bda1820f --- /dev/null +++ b/apps/ideaspace/src/components/common/SubmissionAlert/SuccessAlert.js @@ -0,0 +1,121 @@ +import React, { useState, useEffect } from 'react'; +import { atoms } from '@devlaunchers/components/src/components'; +import styled from 'styled-components'; + +const AlertWrapper = styled.div` + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + background-color: #c4ebc6; + width: 928px; + height: 56px; + border: 2px solid #226626; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + animation: slideDown 0.5s ease-out; + + @keyframes slideDown { + from { + transform: translate(-50%, -100%); + opacity: 0; + } + to { + transform: translate(-50%, 0); + opacity: 1; + } + } +`; + +const AlertContent = styled.div` + display: flex; + align-items: center; + gap: 8px; + flex: 1; +`; + +const AlertMessage = styled.h4` + color: #206124; + margin: 0; + text-align: left; + font-family: 'Nunito Sans', sans-serif; + font-size: 14px; + font-weight: 400; + line-height: 20px; +`; + +const CloseButton = styled.button` + background: none; + border: none; + cursor: pointer; + color: #226626; + padding: 4px; + margin-left: auto; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + opacity: 0.8; + } +`; + +const SuccessAlert = ({ onClose }) => { + const [countdown, setCountdown] = useState(5); + + useEffect(() => { + const timer = setInterval(() => { + setCountdown((prev) => { + if (prev <= 1) { + clearInterval(timer); + } + return prev - 1; + }); + }, 1000); + + return () => clearInterval(timer); + }, []); + + return ( + + + + + + + Your idea was successfully posted! You'll be redirected to the Idea + Workshopping Page in {countdown} seconds. + + + + + + + + + ); +}; + +export default SuccessAlert; diff --git a/apps/ideaspace/src/components/modules/SubmissionForm/StyledSubmissionForm.js b/apps/ideaspace/src/components/modules/SubmissionForm/StyledSubmissionForm.js index 77866e4a5..910e39444 100644 --- a/apps/ideaspace/src/components/modules/SubmissionForm/StyledSubmissionForm.js +++ b/apps/ideaspace/src/components/modules/SubmissionForm/StyledSubmissionForm.js @@ -1,7 +1,166 @@ -import styled from "styled-components"; +import styled from 'styled-components'; export const StyledRanbow = styled.div` margin: 1.3rem auto 0 auto; max-width: 24.8rem; height: 5px; -`; \ No newline at end of file +`; + +export const TextAreaWrapper = styled.div` + position: relative; + width: 100%; + border: 4px solid + ${({ hasError, isCompleted, isFocused }) => { + if (isFocused) return '#3F1F5F'; + if (hasError) return '#882D2D'; + if (isCompleted) return '#1B501D'; + return '#494949'; + }}; + border-radius: 24px; + background-color: ${({ theme }) => theme.colors.white}; + overflow: hidden; + &:hover { + border-color: ${({ theme }) => theme.colors.primary}; + } +`; + +export const FieldWrapper = styled.div` + display: flex; + flex-direction: column; + width: 100%; + max-width: 560px; + margin-bottom: 32px; +`; + +export const FieldLabel = styled.label` + display: flex; + align-items: center; + gap: 2px; + color: #1c1c1c; + font: 400 16px/24px 'Nunito Sans', sans-serif; + text-transform: capitalize; + padding: 8px 0; +`; + +export const RequiredAsterisk = styled.span` + color: #ae3a3a; + font-size: 16px; +`; + +export const StyledTextArea = styled.textarea` + width: 100%; + min-height: 192px; + padding: 24px; + border: none; + outline: none; + font: 300 16px/24px 'Nunito Sans', sans-serif; + color: #494949; + resize: vertical; + border-radius: 0px; + display: block; + margin: 0; + + &::placeholder { + color: var(--static-content-text-grey-muted, #494949); + font-family: var(--font-family-secondary, 'Nunito Sans'); + font-size: var(--placeholder-font-size, 18px); + font-style: normal; + font-weight: 300; + line-height: var(--placeholder-line-height, 24px); + letter-spacing: var(--font-letter-spacing-body-body, 0px); + } +`; + +export const CharacterCounter = styled.div` + text-align: right; + color: #7339ac; + font: 400 14px/20px 'Nunito Sans', sans-serif; + padding: 4px 16px; + color: ${({ isLimit }) => (isLimit ? '#ae3a3a' : '#7339ac')}; +`; + +export const StyledInput = styled.input` + width: 100%; + min-height: 60px; + padding: 24px; + border: none; + outline: none; + font: 300 16px/24px 'Nunito Sans', sans-serif; + color: #494949; + border-radius: 24px; + + &::placeholder { + color: var(--static-content-text-grey-muted, #494949); + font-family: var(--font-family-secondary, 'Nunito Sans'); + font-size: var(--placeholder-font-size, 18px); + font-style: normal; + font-weight: 300; + line-height: var(--placeholder-line-height, 24px); + letter-spacing: var(--font-letter-spacing-body-body, 0px); + } +`; + +export const GroupWrapper = styled.fieldset` + border: none; + padding: 0; + margin: 0 0 32px 0; +`; + +export const GroupLabel = styled.legend` + display: flex; + align-items: center; + gap: 2px; + color: #1c1c1c; + font: 400 16px/24px 'Nunito Sans', sans-serif; + padding: 8px 0; +`; + +export const RadioButton = styled.div` + display: flex; + align-items: center; + gap: 12px; + background-color: #fff; + border-radius: 16px; + padding: 24px; + margin-top: 8px; + @media (max-width: 991px) { + padding: 20px; + } +`; + +export const RadioInput = styled.input` + appearance: none; + width: 24px; + height: 24px; + border: 2px solid #52287a; + border-radius: 50%; + outline: none; + cursor: pointer; + + &:checked { + background-color: #52287a; + border: 6px solid #fff; + box-shadow: 0 0 0 2px #52287a; + } +`; + +export const RadioLabel = styled.label` + font: 400 14px/20px 'Nunito Sans', sans-serif; + color: #303030; +`; + +export const ErrorText = styled.div` + text-align: right; + color: #ae3a3a; + font: 400 14px/20px 'Nunito Sans', sans-serif; + padding: 4px 16px; + margin-top: 4px; +`; + +export const SuccessText = styled.div` + text-align: right; + color: #28a745; + font: 400 14px/20px 'Nunito Sans', sans-serif; + padding: 4px 16px; + margin-top: 4px; +`; diff --git a/apps/ideaspace/src/components/modules/SubmissionForm/SubmissionForm.js b/apps/ideaspace/src/components/modules/SubmissionForm/SubmissionForm.js index 61203ee34..f30df0a4b 100644 --- a/apps/ideaspace/src/components/modules/SubmissionForm/SubmissionForm.js +++ b/apps/ideaspace/src/components/modules/SubmissionForm/SubmissionForm.js @@ -44,11 +44,35 @@ function SubmissionForm() { }; const SignupSchema = Yup.object().shape({ - ideaName: Yup.string().trim().required('Idea Name is Required.'), - description: Yup.string().trim().required('Idea Description is Required.'), - experience: Yup.string().trim().required('Experience is Required.'), - features: Yup.string().trim().required('Idea Feature is Required.'), - involveLevel: Yup.string().required('Level of involvement is Required.'), + ideaName: Yup.string() + .trim() + .required('It looks like you missed a field.') + .max(80, 'Idea Name must be at most 80 characters'), + description: Yup.string() + .trim() + .required('It looks like you missed a field.') + .max(1000, 'Description must be at most 1000 characters'), + experience: Yup.string() + .trim() + .required('It looks like you missed a field.') + .max(1000, 'Experience must be at most 1000 characters'), + targetAudience: Yup.string() + .trim() + .required('It looks like you missed a field.') + .max(1000, 'Target Audience must be at most 1000 characters'), + features: Yup.string() + .trim() + .required('It looks like you missed a field.') + .max(1000, 'Features must be at most 1000 characters'), + extraInfo: Yup.string() + .trim() + .max(1000, 'Extra Info must be at most 1000 characters'), + tagline: Yup.string() + .trim() + .max(80, 'Tagline must be at most 80 characters'), + involveLevel: Yup.string() + .nullable() + .required('Please select your level of involvement'), }); const submitHandler = async (values) => { diff --git a/apps/ideaspace/src/images/check-circle.svg b/apps/ideaspace/src/images/check-circle.svg new file mode 100644 index 000000000..894679a2c --- /dev/null +++ b/apps/ideaspace/src/images/check-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/ideaspace/src/images/x.svg b/apps/ideaspace/src/images/x.svg new file mode 100644 index 000000000..8afcad2f7 --- /dev/null +++ b/apps/ideaspace/src/images/x.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/website/src/components/modules/Home/Home.js b/apps/website/src/components/modules/Home/Home.js index 5742f751f..75b6e79f7 100644 --- a/apps/website/src/components/modules/Home/Home.js +++ b/apps/website/src/components/modules/Home/Home.js @@ -1,213 +1,25 @@ -/* eslint-disable react/no-unescaped-entities */ -import React, { useRef } from "react"; -import { toast } from 'react-toastify'; -import { withTheme } from "styled-components"; -import PageBody from "../../common/PageBody"; - -import HeroImage from "./HeroImage"; -import HeroOverlay from "./HeroOverlay"; -import Intro from "./Intro"; -// import Programs from "./Programs"; - -import Contribution from "./Contribution"; -import Partners from "./Partners"; - import { - HomePageBody, - ColoredCtaWrapper, - ColoredCtaEntry, - ColoredCtaEntryImage, - ColoredCtaEntryTitle, - Wrapper, -} from "./StyledHome"; -import CtaDescriptionArea from "./CtaDescriptionArea"; - -import womanComputerImage from "../../../images/people-cutouts/woman-computer.png?webp"; -import manGlassesImage from "../../../images/people-cutouts/man-glasses.png?webp"; -import girlSmilingImage from "../../../images/people-cutouts/girl-smiling.png?webp"; -import boyFrontImage from "../../../images/people-cutouts/boy-front.png?webp"; - -// General scroll-to function -const scrollToRef = (ref) => window.scrollTo(0, ref.current.offsetTop); - -function Home({ theme }) { - // Scroll-to functions - const learnRef = useRef(null); - const leadRef = useRef(null); - const createRef = useRef(null); - const donateRef = useRef(null); - + Hero, + Opportunities, + JoinUs, + TechStack, + Community, + Features, + Donate, +} from './Sections'; + +const Home = () => { return ( - - - - - - - { - scrollToRef(learnRef); - }} - backgroundColor={theme.colors.ACCENT_1} - > - LEARN - - - { - scrollToRef(leadRef); - }} - backgroundColor={theme.colors.ACCENT_2} - > - LEAD - - - { - scrollToRef(createRef); - }} - backgroundColor={theme.colors.ACCENT_4} - > - CREATE - - - { - scrollToRef(donateRef); - }} - backgroundColor={theme.colors.ACCENT_3} - > - DONATE - - - - - -
- - Learn at a pace tailored completely to you! You'll have - hands on help, starting from{" "} - - square one - {" "} - all the way through building complex projects working in agile - teams. Whether you're looking to build clean, professional - apps and sites, or create scalable data-driven solutions, Dev - Launchers has been crafted for YOU. -
- } - titleUnderlineColor={theme.colors.ACCENT_1} - imageSrc={womanComputerImage} - imageOutlineColor={theme.colors.NEUTRAL_1} - /> -
- -

- Change your life while changing the world! Lead ambitious - agile teams designed to engage new learners and solve real - problems, all with guidance and support from people who have - walked the same path. -

-

- You'll gain the soft skills needed to land more senior - roles in your career while managing projects and making - lifelong friends. Join a growing network of software - professionals working to build something great, together. -

-
- } - titleUnderlineColor={theme.colors.ACCENT_2} - imageSrc={manGlassesImage} - imageOutlineColor={theme.colors.NEUTRAL_1} - /> -
- -

- We build awesome things, and you can join us! -

{" "} -

The Dev Launchers Project Ethos:

-
    -
  • Keep it exciting, always
  • -
  • - Encourage people at all levels and from all backgrounds to - learn technical skills -
  • -
  • - When given the chance, experiment with groundbreaking - technology. -
  • -
  • Make the world better
  • -
-
- } - titleUnderlineColor={theme.colors.ACCENT_4} - imageSrc={girlSmilingImage} - imageOutlineColor={theme.colors.NEUTRAL_1} - /> -
- - Dev Launchers is a registered 501(c)(3) not-for-profit - organization. We can’t keep changing lives without your support! -
- } - titleUnderlineColor={theme.colors.ACCENT_1} - imageSrc={boyFrontImage} - imageOutlineColor={theme.colors.NEUTRAL_2} - mainBackgroundColor={theme.colors.NEUTRAL_1} - titleFontColor={theme.colors.NEUTRAL_2} - descriptionBackgroundColor={theme.colors.NEUTRAL_1} - descriptionFontColor={theme.colors.NEUTRAL_2} - /> - -
-
-
+
+ + + + + + + +
); -} +}; -export default withTheme(Home); +export default Home; diff --git a/apps/website/src/components/modules/Home/Sections/CardImagePair.tsx b/apps/website/src/components/modules/Home/Sections/CardImagePair.tsx new file mode 100644 index 000000000..98cd978c3 --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/CardImagePair.tsx @@ -0,0 +1,156 @@ +import React, { useState } from 'react'; +import { ChevronRight } from 'lucide-react'; + +interface CardImagePairProps { + image: string; + altText?: string; + imagePosition: 'top' | 'bottom'; + imageFit?: 'cover' | 'contain' | 'fill'; + imageBorderColor?: string; + cardBackgroundColor?: string; + cardBorderColor?: string; + descriptionTextColor?: string; + title: string; + description: string; + btnText?: string; + btnLink?: string; + onClick?: () => void; +} + +const COLORS = { + description: 'hsla(271, 50%, 74%, 1)', + defaultBorder: 'white', + defaultBackground: 'hsla(270, 51%, 25%, 0.4)', +} as const; + +// Helper function to parse HSLA color and create a new one with different opacity +const modifyHSLAOpacity = (hslaColor: string, newOpacity: number): string => { + // Default fallback if parsing fails + if (!hslaColor.startsWith('hsla(')) return `hsla(0, 0%, 100%, ${newOpacity})`; + + try { + const values = hslaColor.match(/hsla?\(([^)]+)\)/)?.[1].split(','); + if (!values) return `hsla(0, 0%, 100%, ${newOpacity})`; + + const [h, s, l] = values; + return `hsla(${h}, ${s}, ${l}, ${newOpacity})`; + } catch (e) { + return `hsla(0, 0%, 100%, ${newOpacity})`; + } +}; + +const CardImagePair: React.FC = ({ + image, + altText, + imagePosition, + imageFit = 'cover', + imageBorderColor, + cardBackgroundColor, + cardBorderColor, + descriptionTextColor, + title, + description, + btnText, + btnLink, + onClick, +}) => { + const [isHovered, setIsHovered] = useState(false); + + const LAYOUT = { + container: { + base: 'flex sm:max-w-xs grow gap-6', + position: { + top: 'flex-col', + bottom: 'sm:flex-col-reverse flex-col', + }, + }, + image: { + wrapper: `basis-1/2 overflow-hidden rounded-3xl border-4`, + img: (imageFit: 'cover' | 'contain' | 'fill') => + `w-full h-full object-${imageFit}`, + }, + card: { + wrapper: 'flex basis-1/2 flex-col rounded-3xl border-4 p-6', + content: 'flex flex-col gap-1', + title: 'text-2xl text-left font-semibold pt-4 pb-2', + description: 'text-sm text-left font-normal pb-2', + buttonWrapper: 'flex flex-row pt-2 pb-4', + button: 'text-left normal-case', + link: 'flex flex-row gap-2 align-center py-2 text-white rounded-xl text-left text-base normal-case font-medium', + }, + } as const; + + const containerClasses = `${LAYOUT.container.base} ${LAYOUT.container.position[imagePosition]}`; + const imageClasses = LAYOUT.image.img(imageFit); + + // Create shadow color from border color with 0.3 opacity + const shadowColor = modifyHSLAOpacity( + cardBorderColor || COLORS.defaultBorder, + 0.3 + ); + + const imageWrapperStyle = { + borderColor: imageBorderColor || COLORS.defaultBorder, + order: imagePosition === 'top' ? 2 : 1, + }; + + const cardWrapperStyle = { + borderColor: cardBorderColor || COLORS.defaultBorder, + backgroundColor: cardBackgroundColor || COLORS.defaultBackground, + transition: 'all 0.3s ease', + boxShadow: isHovered ? `inset 0 0 42px ${shadowColor}` : 'none', + }; + + const descriptionStyle = { + color: descriptionTextColor || COLORS.description, + }; + + const linkStyle = { + backgroundColor: 'hsla(0, 0%, 0%, 0)', + borderColor: cardBorderColor || COLORS.defaultBorder, + }; + + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > +
+
+ {altText +
+ +
+
+ + ); +}; + +export default CardImagePair; diff --git a/apps/website/src/components/modules/Home/Sections/Carousel.tsx b/apps/website/src/components/modules/Home/Sections/Carousel.tsx new file mode 100644 index 000000000..9206143fa --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/Carousel.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from '@devlaunchers/components/components/Carousel/Carousel'; + +const testimonials = [ + { + content: + 'I joined Dev launchers as a volunteer and it helped me to polish my skills and the experience of working as a team. I was able to showcase my volunteer works in interviews which helped me to land on a new position with a company in a short span of time. Dev launchers provides people from diverse backgrounds the skills and resources necessary to succeed in their careers. I am happy that I made a wise choice by considering volunteer at Dev launchers. You will be surprised at just how much you can gain from taking the plunge.', + author: 'Hredhya M.', + role: 'Previous Lead', + }, + { + content: + "Dev Launchers is a great organization to volunteer with, and I would recommend it to anyone looking for an opportunity to learn new things, grow your skills and experience in Tech and work with great people all around the world. I've been volunteering for 9 months now and it has been a great experience. Members are very collaborative, kind and polite. Whoever wants to learn new stuff, join teams full of great people and work on open source projects that has real impact this is the place for that! One of the best decisions I've ever made.", + author: 'Julie M.', + role: 'Previous Lead', + }, + { + content: + 'Dev Launchers is an excellent organization, which helps the development of both participants and volunteers alike. The management is flexible, competent, open to ideas, and has plenty of knowledge to share. The culture is fantastic, and each meeting is productive and fun.', + author: 'Mohammedi A.', + role: 'Previous Member', + }, + { + content: + 'Dev Launchers is an excellent organization, which helps the development of both participants and volunteers alike. The management is flexible, competent, open to ideas, and has plenty of knowledge to share. The culture is fantastic, and each meeting is productive and fun.', + author: 'James D.', + role: 'Previous Member', + }, +]; + +const TestimonialCarousel = () => { + return ( +
+ {/* Fade effect containers */} +
+
+ + {/* Main carousel container */} +
+ + + {testimonials.map((testimonial, index) => ( + +
+
+

+ {testimonial.content} +

+
+
+ {testimonial.author[0]} +
+
+

+ {testimonial.author} +

+

+ {testimonial.role} +

+
+
+
+
+
+ ))} +
+ +
+ + +
+
+
+
+ ); +}; + +export default TestimonialCarousel; diff --git a/apps/website/src/components/modules/Home/Sections/Community.tsx b/apps/website/src/components/modules/Home/Sections/Community.tsx new file mode 100644 index 000000000..076a3869f --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/Community.tsx @@ -0,0 +1,42 @@ +import TestimonialCarousel from './Carousel'; +import React from 'react'; +import { Section } from './Section'; +import { styles } from '../styles'; +import { sections } from './sections'; + +const stats = [ + { + number: '200+', + description: "Alumni and members who've been part of our journey", + }, + { number: '8+', description: 'Countries represented in our community' }, + { number: '5', description: 'Continents united through technology' }, +]; + +const CommunitySection = () => ( +
+
+

{sections.community.title}

+

+ {sections.community.description} +

+
+
+ {stats.map((stat, index) => ( +
+
+ + {stat.number} + +

+ {stat.description} +

+
+
+ ))} +
+ +
+); + +export default CommunitySection; diff --git a/apps/website/src/components/modules/Home/Sections/Donate.tsx b/apps/website/src/components/modules/Home/Sections/Donate.tsx new file mode 100644 index 000000000..cabc8f2c1 --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/Donate.tsx @@ -0,0 +1,27 @@ +// components/WhyDevLaunchers.js +import React from 'react'; +import { Section } from './Section'; +import { styles } from '../styles'; +import { sections } from './sections'; + +const DonateSection = () => { + return ( +
+
+

{sections.donate.title}

+

+ {sections.donate.description} +

+
+ +
+ ); +}; + +export default DonateSection; diff --git a/apps/website/src/components/modules/Home/Sections/Features.tsx b/apps/website/src/components/modules/Home/Sections/Features.tsx new file mode 100644 index 000000000..16f935355 --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/Features.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { Section } from './Section'; +import { styles } from '../styles'; +import { sections } from './sections'; +import CardImagePair from './CardImagePair'; + +const features = [ + { + image: '/dev_image (3).png', + altText: + 'Group of people working together at a table with laptops, backlit by warm orange lighting', + imagePosition: 'top', + title: "Shape Tomorrow's Tech", + description: + "Explore, contribute, and refine innovative project ideas with our community. Whether you're sharing your own concepts or helping others evolve theirs, every perspective helps turn great ideas into reality.", + descriptionTextColor: 'hsla(230, 41%, 63%, 1)', + btnText: 'Explore Ideas', + btnLink: '/ideaspace', + imageBorderColor: 'hsla(40, 100%, 50%, 1)', + cardBackgroundColor: 'hsla(230, 51%, 25%, 0.25)', + cardBorderColor: 'hsla(230, 41%, 63%, 1)', + onClick: () => console.log('Shape Tomorrow clicked'), + }, + { + image: '/dev_image (4).png', + altText: + 'Glowing digital world map visualization with interconnected network nodes in pink and purple', + imagePosition: 'bottom', + title: 'Meet Our Community', + description: + "Our diverse members support each other to achieve remarkable things. From launching new careers to building innovative solutions, we're creating positive change through technology. Discover the stories and projects shaping our community's success", + descriptionTextColor: 'hsla(230, 41%, 63%, 1)', + btnText: 'See Our Impact', + btnLink: '/about', + imageBorderColor: 'hsla(194, 52%, 67%, 1)', + cardBackgroundColor: 'hsla(230, 51%, 25%, 0.25)', + cardBorderColor: 'hsla(230, 41%, 63%, 1)', + onClick: () => console.log('Meet Community clicked'), + }, + { + image: '/dev_image (5).png', + altText: + 'People working at computer stations in a modern workspace with colorful ambient lighting', + imagePosition: 'top', + title: 'Hands-on Learning', + description: + "Grow your skills through our collection of resources and hands-on projects. Whether you're exploring development, design, product management, QA, or research, build confidence with real-world examples and guided challenges.", + descriptionTextColor: 'hsla(230, 41%, 63%, 1)', + btnText: 'Explore Resources', + btnLink: '/resources', + imageBorderColor: 'hsla(28, 100%, 53%, 1)', + cardBackgroundColor: 'hsla(230, 51%, 25%, 0.25)', + cardBorderColor: 'hsla(230, 41%, 63%, 1)', + onClick: () => console.log('Hands-on Learning clicked'), + }, +]; + +const Features = () => { + return ( +
+
+

{sections.features.title}

+

+ {sections.features.description} +

+
+
+ {features.map((item, index) => ( + + ))} +
+
+ ); +}; + +export default Features; diff --git a/apps/website/src/components/modules/Home/Sections/Hero.tsx b/apps/website/src/components/modules/Home/Sections/Hero.tsx new file mode 100644 index 000000000..398166e59 --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/Hero.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Section } from './Section'; +import { styles } from '../styles'; +import { sections } from './sections'; + +const HeroSection = () => ( +
+
+

+ {sections.hero.title} +

+

{sections.hero.description}

+
+ +
+

{sections.hero.label}

+
+ Google Logo + Krafties Logo + Microsoft Logo +
+
+
+); + +export default HeroSection; diff --git a/apps/website/src/components/modules/Home/Sections/JoinUs.tsx b/apps/website/src/components/modules/Home/Sections/JoinUs.tsx new file mode 100644 index 000000000..2d55284a9 --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/JoinUs.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Section } from './Section'; +import { styles } from '../styles'; +import { sections } from './sections'; + +const JoinUsSection = () => ( +
+
+

{sections.join.title}

+

{sections.join.description}

+
+ +
+); + +export default JoinUsSection; diff --git a/apps/website/src/components/modules/Home/Sections/Opportunities.tsx b/apps/website/src/components/modules/Home/Sections/Opportunities.tsx new file mode 100644 index 000000000..51057eda8 --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/Opportunities.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { Section } from './Section'; +import { styles } from '../styles'; +import { sections } from './sections'; +import CardImagePair from './CardImagePair'; + +const opportunities = [ + { + image: '/dev_image.png', + altText: + 'Close-up of a finger interacting with a glowing blue digital circuit interface', + imagePosition: 'top', + title: 'Build Your Beginning', + description: + 'Start your tech career or switch paths with hands-on experience at Dev Launchers. Participate in impactful projects and receive mentorship to gain confidence and skills needed in the tech industry.', + descriptionTextColor: 'hsla(271, 50%, 74%, 1)', + btnText: 'Entry-Level Opportunities', + btnLink: '/join', + imageBorderColor: 'hsla(28, 100%, 53%, 1)', + cardBackgroundColor: 'hsla(270, 51%, 25%, 0.25)', + cardBorderColor: 'hsla(270, 41%, 65%, 1)', + onClick: () => console.log('Build Your Beginning clicked'), + }, + { + image: '/dev_image (1).png', + altText: + 'Person presenting to team members in front of a wall covered with colorful sticky notes', + imagePosition: 'bottom', + title: 'Launch to Leadership', + description: + 'Lead teams and mentor emerging talent. Share your experience, guide projects, and help shape the future of tech innovation. Develop leadership skills and advance your career with us.', + descriptionTextColor: 'hsla(271, 50%, 74%, 1)', + btnText: 'Leadership Opportunities', + btnLink: '/join', + imageBorderColor: 'hsla(194, 52%, 67%, 1)', + cardBackgroundColor: 'hsla(270, 51%, 25%, 0.25)', + cardBorderColor: 'hsla(270, 41%, 65%, 1)', + onClick: () => console.log('Launch to Leadership clicked'), + }, + { + image: '/dev_image (2).png', + altText: + 'Professional handshake silhouetted against vibrant blue and pink lighting', + imagePosition: 'top', + title: 'Shape the Future', + description: + 'Become a catalyst for change by becoming a mentor or partnering with us. Help us build an inclusive and innovative tech sector through resources and expertise.', + descriptionTextColor: 'hsla(271, 50%, 74%, 1)', + btnText: 'Partnership Opportunities', + btnLink: '/support-us', + imageBorderColor: 'hsla(40, 100%, 50%, 1)', + cardBackgroundColor: 'hsla(270, 51%, 25%, 0.25)', + cardBorderColor: 'hsla(270, 41%, 65%, 1)', + onClick: () => console.log('Shape the Future clicked'), + }, +]; + +const Opportunities = () => { + return ( +
+
+

+ {sections.opportunities.title} +

+

+ {sections.opportunities.description} +

+
+
+ {opportunities.map((item, index) => ( + + ))} +
+
+ ); +}; + +export default Opportunities; diff --git a/apps/website/src/components/modules/Home/Sections/Section.tsx b/apps/website/src/components/modules/Home/Sections/Section.tsx new file mode 100644 index 000000000..f948e6c8a --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/Section.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { styles } from '../styles'; + +export const Section = ({ children, className = '', style = {} }) => ( +
+ {children} +
+); diff --git a/apps/website/src/components/modules/Home/Sections/TechStack.tsx b/apps/website/src/components/modules/Home/Sections/TechStack.tsx new file mode 100644 index 000000000..75d4c1a89 --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/TechStack.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import { Section } from './Section'; + +import { sections } from './sections'; +import { + Github, + Figma, + TypeScript, + Tailwind, + ReactLogo, + ShadcnLogo, +} from '@devlaunchers/components/assets/icons'; +import { styles } from '../styles'; + +const techStack = [ + { + name: 'Github', + src: '/github.svg', + alt: 'Github Logo', + link: 'https://github.com/dev-launchers/dev-launchers-platform', + color: 'text-white hover:text-purple-500 group-hover:fill-purple-500', + Icon: , + }, + { + name: 'React', + src: '/react.svg', + alt: 'React Logo', + link: 'https://reactjs.org', + color: 'text-white hover:text-purple-500 hover:fill-purple-500', + Icon: , + }, + { + name: 'Tailwind', + src: '/tailwind.svg', + alt: 'Tailwind Logo', + link: 'https://tailwindcss.com', + color: 'text-white hover:text-purple-500 group-hover:fill-purple-500', + Icon: , + }, + { + name: 'TypeScript', + src: '/typescript.svg', + alt: 'TypeScript Logo', + link: 'https://www.typescriptlang.org', + color: 'text-white hover:text-purple-500 group-hover:fill-purple-500', + Icon: , + }, + { + name: 'Figma', + src: '/figma.svg', + alt: 'Figma Logo', + link: 'https://www.figma.com', + color: 'text-white hover:text-purple-500 group-hover:fill-purple-500', + Icon: , + }, + { + name: 'Shadcn', + src: '/shadcn.svg', + alt: 'Shadcn Logo', + link: 'https://ui.shadcn.com', + color: 'text-white hover:text-purple-500 group-hover:fill-purple-500', + Icon: , + }, +]; + +const TechStackSection = () => ( +
+
+

{sections.build.title}

+

{sections.build.description}

+
+ + +
+ {techStack.map(({ name, alt, link, color, Icon }) => ( + + {Icon} + {name} + + ))} +
+
+); + +export default TechStackSection; diff --git a/apps/website/src/components/modules/Home/Sections/index.ts b/apps/website/src/components/modules/Home/Sections/index.ts new file mode 100644 index 000000000..12ae978c2 --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/index.ts @@ -0,0 +1,7 @@ +export { default as Hero } from './Hero'; +export { default as Opportunities } from './Opportunities'; +export { default as JoinUs } from './JoinUs'; +export { default as TechStack } from './TechStack'; +export { default as Community } from './Community'; +export { default as Features } from './Features'; +export { default as Donate } from './Donate'; diff --git a/apps/website/src/components/modules/Home/Sections/sections.js b/apps/website/src/components/modules/Home/Sections/sections.js new file mode 100644 index 000000000..1e2173647 --- /dev/null +++ b/apps/website/src/components/modules/Home/Sections/sections.js @@ -0,0 +1,46 @@ +export const sections = { + hero: { + title: 'Collaborate, Build, and Thrive in Tech', + description: + "Join our global community of 200+ members who've launched their careers to the next level through collaborative, real-world projects.", + label: 'Partners and Sponsors', + buttonText: 'Explore Opportunities', + buttonLink: 'Explore Our Github', + }, + opportunities: { + title: 'Opportunities for Everyone', + description: + "Whether you're starting your journey, leading teams, or seeking to give back, we provide the hands-on experience, mentorship, and community you need to succeed.", + }, + join: { + title: 'Join Us', + description: + "Create your profile, discover opportunities, and join our community—all for free! Whether you're just starting out, looking to switch careers, or seeking new challenges, our community is here to support you every step of the way.", + buttonText: 'Join Today', + buttonLink: 'Explore Our Github', + }, + features: { + title: 'Why Dev Launchers?', + description: + "Transform your tech career through real-world experience, collaborative projects, and supportive community. We provide the hands-on opportunities and resources you need to succeed in today's tech industry.", + }, + build: { + title: 'We Build with the Best', + description: + 'We collaborate and build using the best tools and software including Figma, React, Next.js and more to launch our members to the next level. Learn how we leverage these technologies in real-world projects and develop competitive skills that keep you at the forefront of the industry.', + buttonText: 'Explore Our Github', + buttonLink: 'Explore Our Github', + }, + community: { + title: 'Our Community', + description: + 'From career-launching success stories to global collaborations, our tight-knit community drives innovation in tech. Our alumni work at industry leaders like Amazon, Microsoft, Meta, and J.P. Morgan, while our active members collaborate across five continents to build impactful solutions.', + }, + donate: { + title: 'Transform Tech Today', + description: + "Together, we're building pathways into technology careers for everyone. Your tax-deductible donation helps create an inclusive community where aspiring developers, designers, product managers, researchers, and QA professionals can turn their potential into impact.", + buttonText: 'Donate Today', + buttonLink: 'Explore Our Github', + }, +}; diff --git a/apps/website/src/components/modules/Home/styles.js b/apps/website/src/components/modules/Home/styles.js new file mode 100644 index 000000000..0365e88f0 --- /dev/null +++ b/apps/website/src/components/modules/Home/styles.js @@ -0,0 +1,44 @@ +export const styles = { + main: 'relative min-h-screen bg-black text-white flex flex-col gap-y-16 px-4 pt-20 md:px-6 py-12', + section: { + button: + 'bg-purple-900 border-2 border-purple-400 text-base text-white font-normal capitalize py-3 px-6 rounded-xl transition-all duration-300 hover:text-black hover:font-semibold hover:bg-purple-400 hover:border-purple-300', + buttonContainer: 'flex justify-center py-6', + cardsContainer: + 'flex flex-wrap justify-items-center justify-center my-4 pt-16 gap-8', + communityCard: + 'col-span-1 relative rounded-xl p-[2px] bg-gradient-to-r from-blue-300 to-purple-300', + communityCardContainer: + 'grid grid-cols-1 md:grid-cols-3 gap-6 pt-16 max-w-5xl mx-auto', + communityCardDescription: 'text-gray-500 text-base font-normal', + communityMetrics: + 'h-full w-full bg-black rounded-xl p-6 flex flex-col items-center justify-center text-center', + communityNumber: + 'text-4xl font-bold bg-gradient-to-r from-blue-300 to-purple-300 bg-clip-text text-transparent', + container: + 'container max-w-6xl flex flex-col pb-20 justify-center items-center px-4 pt-20 md:px-6 py-12', + grid: 'grid grid-cols-1 w-full md:grid-cols-3 gap-6', + header: + 'flex flex-col items-center justify-center w-full text-center max-w-screen-xl ', + headerHero: + 'flex flex-col items-center justify-center w-full max-w-6xl text-center pt-6 pb-4', + heading: 'text-6xl font-bold text-center leading-relaxed pb-4', + headingHero: { + className: + 'font-bold max-w-5xl text-center align-center border-none border-0 m-0 pt-0 pb-4', + style: { fontSize: '4.25rem' }, + }, + label: + 'text-lg text-center uppercase text-gray-400 mx-auto font-normal min-w-full', + logoContainer: + 'justify-center pb-10 p-2 grid grid-cols-1 md:grid-cols-3 gap-x-12 gap-y-4 align-items', + logoStyle: 'h-16 w-auto flex-shrink-0', + partnerContainer: 'w-full max-w-7xl mx-auto pt-20 px-4', + partnerLogos: + 'flex flex-col md:flex-row items-center justify-around gap-20 mt-8', + stackLogo: + 'flex items-center justify-center gap-4 p-4 rounded-lg transition-colors', + subHeading: + 'text-xl pb-2 text-center text-gray-500 max-w-3xl mx-auto font-normal', + }, +}; diff --git a/apps/website/src/components/modules/SupportUs/SupportUs.js b/apps/website/src/components/modules/SupportUs/SupportUs.js index f6f76345f..967b61365 100644 --- a/apps/website/src/components/modules/SupportUs/SupportUs.js +++ b/apps/website/src/components/modules/SupportUs/SupportUs.js @@ -1,57 +1,111 @@ -import React from "react"; - -import PageBody from "../../common/PageBody"; -import Button from "../../common/Button"; - -import SupportUsOverlay from "./SupportUsOverlay"; - -import studentPhoto from "../../../images/support-us/bw-girl-profile.png?webp"; -import { - ContentArea, - StudentImageWrapper, - StudentImage, - CtaWrapper, - CtaText, - CtaHeaderArea, - CtaHeaderMessage, - CtaButtonWrapper, - StatusNote, - Wrapper, -} from "./StyledSupportUs"; - export default function SupportUs() { return ( - - - - - - - - - - Support Us - - - Dev Launchers is a 501(c)(3) nonprofit intent on empowering - diverse young creatives, innovators, and leaders. We're - working on touching as many lives as we possibly can, and need - your help to make that happen. Any amount helps! - - - - - - Dev Launchers is a Texas 501(c)(3) nonprofit corporation. - - - - - +
+
+
+

+ Fuel the Future +

+

+ Help Dev Launchers provide resources and opportunity for developers, + creatives, and innovators from diverse backgrounds to gain the + skills and knowledge necessary to launch their careers to the next + level. +

+
+
+
+
+

+ Support Our Mission +

+

+ Dev Launchers is a 501(c)(3) nonprofit intent on empowering diverse + creatives, innovators, and leaders. We're working on touching as + many lives as we possibly can, and need your help to make that + happen. Any amount helps! +

+ +
+
+
+
+

+ Partner with Us +

+

+ At Dev Launchers, we believe in the power of collaboration to create + transformative experiences. Join us in our mission to bridge + opportunity gaps in the tech industry by becoming a partner. Your + involvement can help provide essential resources, mentorship, and + opportunities to those who are eager to learn and grow in their + careers. Whether you are a company, a professional group, or an + individual with a passion for technology and education, your support + is crucial in building a more inclusive and diverse tech community. +

+ +
+
+
+
+

+ Become a Mentor +

+

+ Share your expertise and inspire tech enthusiasts at every career + stage by becoming a mentor at Dev Launchers. As a mentor, you play a + crucial role in helping our diverse members navigate the + complexities of technology and design, guiding them to the next + level in their careers. Your involvement not only shapes their + professional journey but also enhances your leadership and teaching + skills within a collaborative and supportive environment. Whether + you're leading workshops or guiding a team, your contribution helps + turn potential into significant career growth. Join us to foster a + space where every interaction is an opportunity for mutual learning + and every challenge a chance to advance together. +

+ +
+
+
); } diff --git a/apps/website/src/pages/about.js b/apps/website/src/pages/about.js new file mode 100644 index 000000000..8edd87439 --- /dev/null +++ b/apps/website/src/pages/about.js @@ -0,0 +1,374 @@ +import { Donate } from '../../../website/src/components/modules/Home/Sections'; +import Head from 'next/head'; + +export default function About() { + return ( + <> + + About Us + +
+
+
+

+ Our Mission +

+

+ At Dev Launchers, we empower individuals to excel in their tech + careers at every stage of their journey. We believe that + technology careers should be accessible to everyone, regardless of + their background or starting point. +

+
+
+
+
+

+ Our Story +

+

+ Founded in 2019 in Austin, Texas, Dev Launchers began as a + Saturday program teaching coding and game development to local + teenagers. What started as a small group of passionate volunteers + at the Austin Central Library has grown into a global community of + developers, designers, and technology enthusiasts. When the world + shifted in 2020, so did we – transitioning to a fully online + platform that opened our doors to members worldwide. This pivot + allowed us to expand our mission beyond geographical boundaries + and age restrictions, creating a truly inclusive space for anyone + interested in technology. Today, Dev Launchers operates with 6 + dedicated teams and over 50 active members across the globe, from + the United States to Australia, united by our mission to + democratize access to technology careers and launch our members to + the next level in their careers through experience and community. +

+
+
+
+
+

+ What We Do +

+

+
+ +
+
+

+ Create +

+

+ We're developing a platform to foster new projects, where ideas + come to life through collaboration and shared learning. Our + members work on real-world projects, gaining practical + experience in software development, design, and project + management. +

+
+
+

+ Connect +

+

+ We build professional networks and forge friendships across the + globe. Our community spans multiple continents, bringing + together diverse perspectives and experiences. We believe great + accomplishments come from teamwork. +

+
+
+

+ Grow +

+

+ We mentor new members, encourage leadership development, and + expand our community. Learning from mistakes is part of our + journey, and we celebrate both the successes and the learning + opportunities along the way. +

+
+
+

+ Have Fun! +

+

+ We believe that engagement is the gateway to learning, and + learning should be enjoyable. While we tackle challenging + problems, we maintain an environment that promotes creativity, + curiosity, and enjoyment. +

+
+
+
+ +
+
+

+ Our Values +

+

+ At Dev Launchers, our values shape how we work, learn, and grow + together. We believe that the best experiences come from combining + excitement and intention - making learning fun while staying + committed to continuous improvement. We foster deep connections + and emphasize teamwork, knowing that our greatest achievements + come from working together. Our community is built on the + foundation of mutual support, where asking for help is celebrated + and every success is shared. +

+
+ +
+
+

+ Excitement +

+

+ Excitement drives engagement and fuels learning. When you're + genuinely excited about what you're creating, learning happens + naturally. We create an environment where curiosity is + celebrated and enthusiasm is contagious. +

+
+
+

+ Intention +

+

+ Success isn't about being the smartest or the most talented – + it's about showing up every day with purpose and dedication. We + approach challenges methodically, knowing that persistent, + intentional effort leads to breakthrough solutions. +

+
+
+

+ Connection +

+

+ In our community, reaching out for help is a sign of strength, + not weakness. Strong connections form the foundation of + effective learning and innovation. We celebrate collaboration + and understand that our diverse perspectives make us stronger + together. +

+
+
+

+ Building +

+

+ Progress isn't always linear, and success looks different for + everyone. We encourage thoughtful self-assessment and believe in + setting meaningful, personal benchmarks for growth. Through + iterative development, we build both our projects and ourselves. +

+
+
+

+ Teamwork +

+

+ Individual achievements are team victories. We foster an + environment where success is shared and celebrated collectively. + Respect isn't just an ideal – it's woven into how we collaborate + and support each other every day. +

+
+
+

+ Leadership +

+

+ Leadership isn't about titles; it's about taking initiative and + responsibility. Whether you're mentoring others, leading a + project, or suggesting improvements, we encourage everyone to + step into leadership roles and make positive changes. +

+
+
+
+
+
+

+ Partner with Us +

+

+ At Dev Launchers, we believe in the power of collaboration to + create transformative experiences. Join us in our mission to + bridge opportunity gaps in the tech industry by becoming a + partner. Your involvement can help provide essential resources, + mentorship, and opportunities to those who are eager to learn and + grow in their careers. Whether you are a company, a professional + group, or an individual with a passion for technology and + education, your support is crucial in building a more inclusive + and diverse tech community. +

+
+ +
+
+
+

+ Our Partners and Sponsors +

+
+ +

+
+
+

+ Powered by Leading Technology Companies +

+

+ Dev Launchers is proud to be supported by grants from industry + leaders who share our vision of making technology careers + accessible to everyone. Through the generous support of Google + and Microsoft, we're able to expand our reach and enhance our + programs while maintaining our commitment to providing free, + high-quality technology education and project-based learning + opportunities. +

+
+
+

+ Community Partnerships +

+

+ Our roots in the Austin community run deep, starting with our + founding partnership with the Austin Public Library. This + collaboration helped launch our initial programming and + continues to inspire our mission of democratizing access to + technology education. We're also proud to partner with + organizations like Aseprite and Krafties, who help us provide + our members with the tools and resources they need to bring + their creative visions to life. +

+
+
+

+ Building Together +

+

+ These partnerships represent more than just financial support – + they're relationships that help us create meaningful + opportunities for our community. Our partners share our + commitment to making technology education accessible to + everyone, supporting diverse talent in the tech industry, + creating hands-on learning opportunities, building pathways to + technology careers, and fostering an inclusive tech community. + Together with our partners, we're working to break down barriers + and create a more equitable tech ecosystem where everyone has + the opportunity to learn, grow, and succeed. +

+
+
+
+ +
+
+

+ Our Teams +

+

+ {' '} + Our organization is entirely powered by our volunteer members + dedicated to launching their own careers as well as supporting + others building our collective, supportive and innovative + community. +

+
+ +
+
+

+ Dev Recruit +

+

+ {' '} + Our internal recruitment platform that powers our teams through + providing recruitment management and support. +

+
+
+

+ IdeaSpace +

+

+ Turn dreams into software ideas, a place to discover new ideas + or workshop your ideas and turn them into reality. +

+
+
+

+ Platform +

+

+ {' '} + Our core development team that supports our community platform + through full-stack developed assets. +

+
+
+

+ User Profile +

+

+ Powering the profile features and enabling connection and + collaboration support for our membership community. +

+
+
+

+ AI Ally +

+

+ {' '} + Leveraging AI capabilities to improve our internal processes and + member workflow. +

+
+
+

+ Universal Design +

+

+ {' '} + Building and maintaining our design system and brand identity + for the organization while providing assets and framework to + power our teams creativity to build unlocked +

+
+
+
+ + +
+ + ); +} diff --git a/apps/website/src/pages/collaborate.js b/apps/website/src/pages/collaborate.js new file mode 100644 index 000000000..12d5ec783 --- /dev/null +++ b/apps/website/src/pages/collaborate.js @@ -0,0 +1,160 @@ +import Head from 'next/head'; + +export default function Collaborate() { + return ( + <> + + Collaborate + +
+
+
+

+ Collaborate with Us +

+

+ Collaborate with us to build real-world projects and gain + invaluable experience. Whether you're a beginner or a seasoned + professional your journey starts here! +

+
+
+
+
+

+ Choose Your Journey +

+

+ Our community is diverse, and so are the ways you can join us. We + understand that everyone has different goals and availability, + which is why we offer a variety of membership pathways to support + each member uniquely. Select your path at Dev Launchers with our + tailored membership levels. We have a place for you whether you + want to grow independently in our Orbit, transition between levels + as a Traveler, or empower our core initiatives. +

+
+
+

+ Orbit +

+

+ Engage with our community at your own pace, access resources, + and join our network. +

+
+
+

+ Traveler +

+

+ Dive deeper with flexible project participation and special + events that suit your schedule. +

+
+
+

+ Core +

+

+ Take a central role in our projects, working regularly as a + team member or leader on impactful software projects. This + path accelerates your growth through hands-on, + cross-functional team experiences. +

+
+
+
+
+ +
+
+

+ Create your Profile +

+

+ Create your profile and connect with like-minded individuals in + our vibrant community. Engage with other members, share your + journey, and find partners for your next project. Networking at + Dev Launchers opens doors to opportunities and collaborations. +

+ +
+
+
+
+

+ Learn with Resources +

+

+ Utilize our comprehensive library of resources, templates, and + tools to enhance your skillset. From beginner to advanced levels, + our learning materials are designed to support your growth in the + most dynamic industries. +

+ +
+
+
+
+

+ Dream with IdeaSpace +

+

+ Members brainstorm, propose, and workshop on software project + ideas. Got a groundbreaking idea? Bring it to our community and + see it take shape! +

+ +
+
+
+
+

+ Join with Dev Recruit +

+

+ Looking to dive deeper into tech and gain practical experience? + Explore our Dev Recruit platform for available opportunities + designed to hone your skills through intensive and collaborative + project work. Gain hands-on experience under the guidance of our + seasoned mentors and prepare for a career in technology. +

+ +
+
+
+ + ); +} diff --git a/apps/website/src/pages/resources.js b/apps/website/src/pages/resources.js new file mode 100644 index 000000000..d5aef9cd0 --- /dev/null +++ b/apps/website/src/pages/resources.js @@ -0,0 +1,587 @@ +import Head from 'next/head'; +import { useState } from 'react'; +import 'react-tabs/style/react-tabs.css'; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationJump, + PaginationLink, +} from '@devlaunchers/components/components/Pagination/Pagination'; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from '@devlaunchers/components/components/molecules/Tab/Tab'; +import { useSheetsContext } from '../context/SheetsContext'; + +const Card = ({ href, imageSrc, title, description }) => ( + +
+ {title} +
+
+

+ {title} +

+

+ {description || 'No description available.'} +

+
+ Learn More + + + +
+
+
+); + +const PaginatedGrid = ({ items, itemsPerPage = 4 }) => { + const [currentPage, setCurrentPage] = useState(1); + const totalPages = Math.ceil(items.length / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const currentItems = items.slice(startIndex, endIndex); + const pages = Array.from({ length: totalPages }, (_, i) => i + 1); + + return ( +
+
+ {currentItems.map((item, index) => ( + + ))} +
+ + {totalPages > 1 && ( + + + {currentPage > 1 && ( + <> + + { + e.preventDefault(); + setCurrentPage(1); + }} + /> + + + { + e.preventDefault(); + setCurrentPage((prev) => prev - 1); + }} + /> + + + )} + + {pages.map((page) => ( + + { + e.preventDefault(); + setCurrentPage(page); + }} + > + {page} + + + ))} + + {currentPage < totalPages && ( + <> + + { + e.preventDefault(); + setCurrentPage((prev) => prev + 1); + }} + /> + + + { + e.preventDefault(); + setCurrentPage(totalPages); + }} + /> + + + )} + + + )} +
+ ); +}; + +export default function Resources() { + const { learnPageData, createPageData } = useSheetsContext(); + + return ( + <> + + Resources + +
+
+
+

+ Resources for Growth +

+

+ Embark on a path to expanding your technological prowess. Whether + you are just starting or looking to refine specific skills, our + curated resources are here to guide you every step of the way. + Dive into a comprehensive collection of tools, tutorials, and + hands-on projects designed to enhance your understanding and + practical knowledge. +

+
+
+ +
+
+

+ Resources by Category +

+

+ Explore a diverse array of resources organized by category to find + exactly what you need to progress. From coding tools and design + software to advanced tutorials, our categories are designed to + cater to learners at all levels. +

+
+
+ + + + Development + + + Design + + + Research + + + Project Management + + + +
+

+ Development +

+ +
+
+ +
+

+ Design +

+
    +
  • + + Dev Launchers Design Docs + +

    + A guide to the design principles and practices adopted + by Dev Launchers for creating engaging user interfaces + and experiences. +

    +
  • +
  • + + Figma + +

    + A web-based interface design tool that allows real-time + collaboration among teams for building interactive and + professional designs. +

    +
  • +
  • + + Atomic Design by Brad Frost + +

    + A methodology for creating design systems composed of + atoms, molecules, and organisms to build robust user + interfaces. +

    +
  • +
  • + + Dev Launchers Universal Design System + +

    + This resource showcases the Universal Design System + utilized by Dev Launchers, featuring standardized UI + components and design tokens. +

    +
  • +
+
+
+ +
+

+ Research +

+
    +
  • + + Dev Launchers Research Docs + +

    + Essential reading for understanding the research + processes and methodologies utilized by Dev Launchers. +

    +
  • +
  • + + Nielsen Norman Group Research Methods + +

    + An essential collection of user research methodologies + and best practices offered by the Nielsen Norman Group, + a leader in the UX field. +

    +
  • +
  • + + The UX Research Field Guide + {' '} +

    + A comprehensive guide to doing UX research by User + Interviews +

    +
  • +
  • + + Hotjar + +

    + Hotjar offers tools to visualize user behavior on your + site through heatmaps, session recordings, and surveys, + enabling deeper insights into user interactions and + experiences. +

    +
  • +
+
+
+ +
+

+ Project Management +

+ +
+
+
+
+
+ +
+
+

+ Learning and Games +

+

+ Learn Code and Design using these games to build conceptual + learning to prepare for creating software projects and + applications +

+
+ +
+ + + {Object.keys(learnPageData).map((category) => ( + + {category} + + ))} + + {Object.entries(learnPageData).map(([category, sections]) => ( + + {Object.entries(sections).map( + ([sectionTitle, categories]) => ( +
+

+ {sectionTitle} +

+ {Object.entries(categories).map( + ([categoryTitle, items]) => ( +
+ +
+ ) + )} +
+ ) + )} +
+ ))} +
+
+
+
+
+

+ Create with Templates +

+

+ Ready to test your skills? These templates are great ways to get + started and reinforce your learning +

+
+ +
+ + + {Object.keys(createPageData).map((category) => ( + + {category} + + ))} + + {Object.entries(createPageData).map(([category, sections]) => ( + + {Object.entries(sections).map( + ([sectionTitle, categories]) => ( +
+

+ {sectionTitle} +

+ {Object.entries(categories).map( + ([categoryTitle, items]) => ( +
+ +
+ ) + )} +
+ ) + )} +
+ ))} +
+
+
+
+
+

+ Create and Collaborate +

+

+ Join us in real cross-functional teams to apply your skills to + real world applications +

+ +
+
+
+ + ); +} diff --git a/packages/UI/package.json b/packages/UI/package.json index 645eb96a3..14d2c6d61 100644 --- a/packages/UI/package.json +++ b/packages/UI/package.json @@ -16,8 +16,10 @@ "@radix-ui/react-tabs": "1.0.4", "axios": "^0.27.2", "babel-loader": "8.2.5", + "class-variance-authority": "0.7.1", "clsx": "2.0.0", "constate": "^3.3.2", + "embla-carousel-react": "8.5.1", "formik": "^2.2.9", "framer-motion": "6.5.1", "iso8601-duration": "2.1.2", diff --git a/packages/UI/src/assets/icons/Figma.tsx b/packages/UI/src/assets/icons/Figma.tsx new file mode 100644 index 000000000..103e717e7 --- /dev/null +++ b/packages/UI/src/assets/icons/Figma.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; + +function Figma(props: React.SVGProps) { + return ( + + + + ); +} + +export default Figma; diff --git a/packages/UI/src/assets/icons/Github.tsx b/packages/UI/src/assets/icons/Github.tsx index 04009f0f6..5c888eb2b 100644 --- a/packages/UI/src/assets/icons/Github.tsx +++ b/packages/UI/src/assets/icons/Github.tsx @@ -12,7 +12,7 @@ function Github(props: React.SVGProps) { > ); diff --git a/packages/UI/src/assets/icons/ReactLogo.tsx b/packages/UI/src/assets/icons/ReactLogo.tsx new file mode 100644 index 000000000..02b3cc4c5 --- /dev/null +++ b/packages/UI/src/assets/icons/ReactLogo.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; + +function ReactLogo(props: React.SVGProps) { + return ( + + {/* Center circle */} + + + {/* Orbital rings */} + + + + + + + ); +} + +export default ReactLogo; diff --git a/packages/UI/src/assets/icons/ShadCn.tsx b/packages/UI/src/assets/icons/ShadCn.tsx new file mode 100644 index 000000000..4fcaaff94 --- /dev/null +++ b/packages/UI/src/assets/icons/ShadCn.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; + +function ShadcnLogo(props: React.SVGProps) { + return ( + + + + + ); +} + +export default ShadcnLogo; diff --git a/packages/UI/src/assets/icons/Tailwind.tsx b/packages/UI/src/assets/icons/Tailwind.tsx new file mode 100644 index 000000000..091568ae2 --- /dev/null +++ b/packages/UI/src/assets/icons/Tailwind.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; + +function Tailwind(props: React.SVGProps) { + return ( + + + + + ); +} + +export default Tailwind; diff --git a/packages/UI/src/assets/icons/Typescript.tsx b/packages/UI/src/assets/icons/Typescript.tsx new file mode 100644 index 000000000..94a1b2d2a --- /dev/null +++ b/packages/UI/src/assets/icons/Typescript.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; + +function TypeScript(props: React.SVGProps) { + return ( + + {/* Background */} + + + {/* TypeScript Text */} + + + ); +} + +export default TypeScript; diff --git a/packages/UI/src/assets/icons/index.ts b/packages/UI/src/assets/icons/index.ts index bce810757..9ac35f0a1 100644 --- a/packages/UI/src/assets/icons/index.ts +++ b/packages/UI/src/assets/icons/index.ts @@ -1,15 +1,20 @@ export { default as Bell } from './Bell'; +export { default as Checkmark } from './Checkmark'; export { default as Chevron } from './Chevron'; export { default as Close } from './Close'; +export { default as Discord } from './Discord'; export { default as Error } from './Error'; -export { default as Success } from './Success'; -export { default as Twitter } from './Twitter'; -export { default as Instagram } from './Instagram'; export { default as Facebook } from './Facebook'; -export { default as Slack } from './Slack'; -export { default as Linkedin } from './Linkedin'; +export { default as Figma } from './Figma'; export { default as Github } from './Github'; -export { default as Discord } from './Discord'; +export { default as Instagram } from './Instagram'; export { default as Link } from './Link'; +export { default as LinkedIn } from './Linkedin'; export { default as Mail } from './Mail'; -export { default as Checkmark } from './Checkmark'; +export { default as ReactLogo } from './ReactLogo'; +export { default as ShadcnLogo } from './ShadCn'; +export { default as Slack } from './Slack'; +export { default as Success } from './Success'; +export { default as Tailwind } from './Tailwind'; +export { default as Twitter } from './Twitter'; +export { default as TypeScript } from './Typescript'; diff --git a/packages/UI/src/components/Carousel/Carousel.tsx b/packages/UI/src/components/Carousel/Carousel.tsx new file mode 100644 index 000000000..8943aedee --- /dev/null +++ b/packages/UI/src/components/Carousel/Carousel.tsx @@ -0,0 +1,261 @@ +'use client'; + +import useEmblaCarousel, { + type UseEmblaCarouselType, +} from 'embla-carousel-react'; +import { X, ArrowLeft, ArrowRight } from 'lucide-react'; +import * as React from 'react'; +import { cn } from '../../utils/classesMerger'; +import { Button } from '../atoms'; + +type CarouselApi = UseEmblaCarouselType[1]; +type UseCarouselParameters = Parameters; +type CarouselOptions = UseCarouselParameters[0]; +type CarouselPlugin = UseCarouselParameters[1]; + +type CarouselProps = { + opts?: CarouselOptions; + plugins?: CarouselPlugin; + orientation?: 'horizontal' | 'vertical'; + setApi?: (api: CarouselApi) => void; +}; + +type CarouselContextProps = { + carouselRef: ReturnType[0]; + api: ReturnType[1]; + scrollPrev: () => void; + scrollNext: () => void; + canScrollPrev: boolean; + canScrollNext: boolean; +} & CarouselProps; + +const CarouselContext = React.createContext(null); + +function useCarousel() { + const context = React.useContext(CarouselContext); + + if (!context) { + throw new Error('useCarousel must be used within a '); + } + + return context; +} + +const Carousel = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & CarouselProps +>( + ( + { + orientation = 'horizontal', + opts, + setApi, + plugins, + className, + children, + ...props + }, + ref + ) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === 'horizontal' ? 'x' : 'y', + }, + plugins + ); + const [canScrollPrev, setCanScrollPrev] = React.useState(false); + const [canScrollNext, setCanScrollNext] = React.useState(false); + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return; + } + + setCanScrollPrev(api.canScrollPrev()); + setCanScrollNext(api.canScrollNext()); + }, []); + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev(); + }, [api]); + + const scrollNext = React.useCallback(() => { + api?.scrollNext(); + }, [api]); + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === 'ArrowLeft') { + event.preventDefault(); + scrollPrev(); + } else if (event.key === 'ArrowRight') { + event.preventDefault(); + scrollNext(); + } + }, + [scrollPrev, scrollNext] + ); + + React.useEffect(() => { + if (!api || !setApi) { + return; + } + + setApi(api); + }, [api, setApi]); + + React.useEffect(() => { + if (!api) { + return; + } + + onSelect(api); + api.on('reInit', onSelect); + api.on('select', onSelect); + + return () => { + api?.off('select', onSelect); + }; + }, [api, onSelect]); + + return ( + +
+ {children} +
+
+ ); + } +); +Carousel.displayName = 'Carousel'; + +const CarouselContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel(); + + return ( +
+
+
+ ); +}); +CarouselContent.displayName = 'CarouselContent'; + +const CarouselItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { orientation } = useCarousel(); + + return ( +
+ ); +}); +CarouselItem.displayName = 'CarouselItem'; + +const CarouselPrevious = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel(); + + return ( + + ); +}); +CarouselPrevious.displayName = 'CarouselPrevious'; + +const CarouselNext = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel(); + + return ( + + ); +}); +CarouselNext.displayName = 'CarouselNext'; + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +}; diff --git a/packages/UI/src/components/NotificationItem/NotificationItem.tsx b/packages/UI/src/components/NotificationItem/NotificationItem.tsx index 53df92f6c..55e9d78f6 100644 --- a/packages/UI/src/components/NotificationItem/NotificationItem.tsx +++ b/packages/UI/src/components/NotificationItem/NotificationItem.tsx @@ -11,11 +11,11 @@ const compoundSlots = [ 'descriptionStyle', 'targetStyle', ] as const, - className: 'font-nunito-sans text-sm md:text-base leading-normal', + className: 'font-nunito-sans text-sm leading-normal', }, { slots: ['targetStyle', 'usernameStyle'] as const, - className: 'font-bold', + className: 'font-semibold', }, { slots: ['timeStampStyle', 'descriptionStyle'] as const, @@ -26,30 +26,24 @@ const compoundSlots = [ const notificationStyles = tv({ slots: { wrapper: - 'flex items-center gap-4 py-4 pl-2 pr-4 hover:bg-grayscale-100 md:p-8', - avatarContainer: 'hidden md:inline-flex', - detailsContainer: - 'flex flex-shrink-0 flex-grow basis-0 flex-col items-start gap-2 md:flex-row md:flex-wrap md:content-start', - contentContainer: - 'md:flex md:flex-shrink-0 md:flex-grow md:basis-0 md:flex-col md:items-start md:gap-1', - headerStyle: 'flex items-start gap-1 self-stretch', - usernameStyle: '', - actionStyle: 'leading-normal', - targetStyle: '', - descriptionStyle: 'line-clamp-2 self-stretch md:order-1', - timeStampStyle: - 'text-right font-nunito-sans text-sm md:text-base leading-6 text-grayscale-400', - statusIndicator: 'h-3 w-3 shrink-0 rounded-full', + 'flex items-center gap-4 p-4 border-b border-gray-800 transition-colors duration-200', + avatarContainer: 'flex-shrink-0', + detailsContainer: 'flex flex-grow items-start gap-2 min-w-0', + contentContainer: 'flex flex-col gap-1 min-w-0', + headerStyle: 'flex items-center gap-1 flex-wrap', + usernameStyle: 'text-white', + actionStyle: 'text-gray-300', + targetStyle: 'text-white', + descriptionStyle: 'line-clamp-2 text-gray-300', + timeStampStyle: 'text-sm text-gray-400', }, variants: { status: { read: { - wrapper: 'text-grayscale-500', - statusIndicator: 'invisible', + wrapper: 'bg-transparent hover:bg-gray-800', }, unRead: { - wrapper: 'text-grayscale-900', - statusIndicator: 'bg-alert-notification-o-100-600', + wrapper: 'bg-[#52287A]/10 hover:bg-gray-800', }, }, }, @@ -61,19 +55,10 @@ interface NotificationProps extends VariantProps { name: NotificationUser['data']['attributes']['username']; target: string; targetLink: string; - /** - * TimeStamp in ISO_8601 duration format - * @description it starts with P[duration designator, stands for period) followed by number and Y or M or D then T[time designator] followed by number and H or M or S - * @example "P3Y6M4DT12H30M5S" represents a duration of "three years, six months, four days, twelve hours, thirty minutes, and five seconds". - * @see https://en.wikipedia.org/wiki/ISO_8601#Durations - */ timeStamp: string; action: NotificationEvent['data']['attributes']['action']; - - avatar: { - src: React.ComponentProps['src']; - alt: React.ComponentProps['alt']; - }; + avatar: string; + className?: string; } function timeSincePublished(publishedAt: string) { @@ -111,55 +96,44 @@ function NotificationItem({ targetLink, status, avatar, + className, }: NotificationProps) { - const { - wrapper, - statusIndicator, - headerStyle, - detailsContainer, - contentContainer, - actionStyle, - targetStyle, - descriptionStyle, - timeStampStyle, - } = notificationStyles({ status }); + const styles = notificationStyles({ status }); return ( -
  • +
  • - - - - -
    -
    -
    - {name} +
    + +
    - {actionTexts[action]} -
    - {target} - +
    +
    +
    + {name} + + {actionTexts[action]} + + {target}
    -

    {message}

    +

    {message}

    +
    -
  • diff --git a/packages/UI/src/components/molecules/Tab/Tab.tsx b/packages/UI/src/components/molecules/Tab/Tab.tsx index 843cab1f8..98d58e00d 100644 --- a/packages/UI/src/components/molecules/Tab/Tab.tsx +++ b/packages/UI/src/components/molecules/Tab/Tab.tsx @@ -1,79 +1,55 @@ -import * as RadixTabs from '@radix-ui/react-tabs'; -import { type FC, type ReactNode } from 'react'; -import { tv } from 'tailwind-variants'; +'use client'; -type TabsProps = Omit< - RadixTabs.TabsProps, - 'asChild' | 'defaultValue' | 'content' | 'onValueChange' -> & - Omit & - Omit & { - /** - * The buttons that activate its associated content. Dev Note: USE Trigger COMPONENT FOR BETTER ACCESSIBILITY - */ - triggers: ReactNode; - /** - * The value of the tab that should be active when initially rendered. Use when you do not need to control the state of the tabs. - */ - defaultValue?: string; - /** - * Contains the content associated with each trigger. Dev Note: USE Content COMPONENT FOR BETTER ACCESSIBILITY - */ - children: ReactNode; - /** - * Event handler called when the value changes. - */ - onValueChange?: (value: string) => void; - /** - * The controlled value of the tab to activate. Should be used in conjunction with onValueChange. - */ - value?: string; - /** - * When true, keyboard navigation will loop from last tab to first, and vice versa. - */ - loop?: boolean; - }; +import * as React from 'react'; +import * as TabsPrimitive from '@radix-ui/react-tabs'; -/** - * @description A set of layered sections of content—known as tab panels—that are displayed one at a time. - * https://www.radix-ui.com/primitives/docs/components/tabs/1.0.4 - */ -const Tabs = ({ - loop = true, - triggers, - activationMode, - defaultValue, - onValueChange, - value, - children, - ...props -}: TabsProps) => { - return ( - - - {triggers} - - {children} - - ); -}; -export default Tabs; +import { cn } from '../../../utils/classesMerger'; -const TriggerStyles = tv({ - base: 'inline-flex h-11 w-64 flex-shrink-0 items-center justify-center gap-2 py-3 font-nunito-sans text-[1rem] font-normal text-[#888888] hover:bg-[#f9f9f9] data-[state=active]:border-b-2 data-[state=active]:border-solid data-[state=active]:border-[#3350e5] data-[state=active]:font-bold data-[state=active]:text-[#000000]', -}); +const Tabs = TabsPrimitive.Root; -export const Trigger: FC = ({ - className, - ...props -}) => { - const styles = TriggerStyles({ className }); - return ; -}; +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsList.displayName = TabsPrimitive.List.displayName; -export { Content } from '@radix-ui/react-tabs'; +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; + +export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/packages/UI/src/components/organisms/Footer/Footer.jsx b/packages/UI/src/components/organisms/Footer/Footer.jsx index 2b3b1fd03..ed2364fab 100644 --- a/packages/UI/src/components/organisms/Footer/Footer.jsx +++ b/packages/UI/src/components/organisms/Footer/Footer.jsx @@ -1,81 +1,75 @@ -import Link from 'next/link'; -import { ThemeProvider } from 'styled-components'; -import theme from '../../../styles/theme'; +import { Github, Linkedin } from 'lucide-react'; +import logo from '../../../assets/images/logo-monogram.png'; -import Newsletter from './Newsletter'; -import RandomQuote from './RandomQuote'; -import { - FooterLogo, - FooterNav, - NavEntry, - OrgInfoArea, - SocialMediaContainer, - SocialMediaLink, - Wrapper, -} from './StyledFooter'; - -export default function Footer() { +const Footer = () => { return ( - - - - - - - CREATE - - - - - LEARN + ); -} +}; + +export default Footer; diff --git a/packages/UI/src/components/organisms/Navigation/Navigation.tsx b/packages/UI/src/components/organisms/Navigation/Navigation.tsx index 16c7229cc..a7bf4916b 100644 --- a/packages/UI/src/components/organisms/Navigation/Navigation.tsx +++ b/packages/UI/src/components/organisms/Navigation/Navigation.tsx @@ -1,212 +1,442 @@ +import React from 'react'; import Link from 'next/link'; -import * as React from 'react'; -import { tv } from 'tailwind-variants'; +import { ChevronDown, Menu, X, User, LogOut, Lightbulb } from 'lucide-react'; +import logo from '../../../assets/images/logo-monogram.png'; import { useUserDataContext } from '../../../context/UserDataContext'; import Logout from '../../../utils/Logout'; -import { Button, Layer, NavLink } from '../../atoms'; -import NavDropdown from '../NavDropdown/NavDropdown'; -import logo from './../../../assets/images/logo-monogram.png'; -import MobileNavigation from './MobileNavigation'; import NotificationPopover from './NotificationPopover'; -const LogoutIcon = ({ fill, ...props }: React.SVGAttributes) => { - return ( - - - - ); +// Centralized styles +const styles = { + // Navigation styles + nav: 'sticky relative top-0 flex h-16 items-center justify-between bg-black px-4 md:px-8 z-50 text-lg sm:text-sm', + navItem: + 'text-gray-400 font-normal transition-all duration-200 hover:text-white hover:font-semibold active:text-white active:font-semibold', + + // Logo styles + logoContainer: 'flex items-center gap-4', + logoLink: 'flex items-center gap-3', + logoImage: 'h-8 w-8', + logoText: 'text-white font-semibold hidden md:block', + + // Button styles + buttonPrimary: + 'rounded-lg bg-[#52287A] border border-[#996FC3] px-6 py-2 text-sm font-medium text-white hover:bg-purple-700', + buttonSecondary: + 'rounded-lg border border-purple-600 bg-transparent px-6 py-2 text-sm font-medium text-white hover:bg-purple-600/10', + + // Dropdown styles + dropdownContainer: 'flex flex-row items-center ', + dropdownTrigger: + 'text-gray-300 font-normal transition-all duration-200 hover:text-white hover:text-white hover:font-semibold flex items-center gap-2', + dropdownContent: 'absolute top-8 left-0 w-full border-t border-gray-800 mt-8', + dropdownItem: + 'block rounded-lg bg-[#1C1C1C] p-6 transition-colors hover:bg-gray-800', + dropdownGrid: + 'grid grid-cols-1 lg:grid-cols-2 gap-4 w-full lg:w-[900px] bg-black p-4 mt-2 mx-auto rounded-xl', + + // Mobile menu styles + mobileMenu: + 'fixed inset-y-0 right-0 z-50 w-64 transform bg-[#1C1C1C] p-6 shadow-lg transition-transform duration-300 ease-in-out lg:hidden', + mobileMenuItem: 'block py-2 text-gray-300 hover:text-white', + + // Profile styles + profileContainer: 'flex items-center gap-2 text-white', + profileImage: 'h-8 w-8 rounded-full', + profileDropdown: + 'absolute right-8 top-full mt-2 w-64 rounded-lg bg-[#1C1C1C] py-2 shadow-xl', + profileMenuItem: + 'flex items-center gap-2 px-4 py-2 text-gray-300 hover:bg-gray-700', + + // Icon styles + icon: 'h-5 w-5 text-white', + chevron: 'h-4 w-4 transform transition-transform', + + // Layout styles + desktopNav: 'hidden flex-1 items-center justify-center gap-8 lg:flex', + mobileToggle: 'text-white lg:hidden', }; +const projectItems = [ + { + title: 'Dev Recruit', + description: + 'Dev Recruit allows current and potential members to easily discover and learn about the projects they can join that aligns with their goals and experience', + href: '/projects/dev-recruit', + }, + { + title: 'IdeaSpace', + description: + 'A hub where we can make ideas into reality. Our platform allows Dev Launchers users to vocalize their ideas then build them into a project.', + href: '/projects/DL-IdeaSpace', + }, + { + title: 'Universal Design', + description: + "The Universal Design team's mission is to ensure consistent, high-quality user experiences across all organization products.", + href: '/projects/universal-design', + }, + { + title: 'Platform', + description: + 'Provide the underlying internal components required by our cross-functional teams to deliver higher-level services and functionalities.', + href: '/projects/platform', + }, + { + title: 'User Profile', + description: + 'Crafting a user-friendly profile for our members while facilitating global connections and transforming learning.', + href: '/projects/user-profile', + }, + { + title: 'AI Ally', + description: + 'Leveraging LLMs to build, streamline and support our platform through automating various aspects of our operations and improving our overall efficiency.', + href: '/projects/ai-ally', + }, +]; + +const ProfileDropdown = ({ userData }) => { + const [isOpen, setIsOpen] = React.useState(false); + const dropdownRef = React.useRef(null); + + React.useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen]); -const HamburgerButton = ({ - className, - open, - setOpen, -}: { - open: boolean; - setOpen: React.Dispatch>; - className: string; -}) => { return ( - + + {isOpen && ( +
    +
    +
    + Profile + {userData.name} +
    +
    +
    + + + + Profile + + + + + + Idea Dashboard + + + +
    +
    + )} +
    ); }; -// TODO: Use fonts from figma -const NavigationStyles = tv( - { - slots: { - $wrapper: 'flex h-[100px] items-center justify-between bg-[#1C1C1C] px-8', - $logoContainer: 'flex items-center gap-4 text-white', - $linksContainer: '', - $actionsContainer: '', - }, - variants: {}, - } - // { responsiveVariants: ['sm', 'md'] } -); - -export const links = { - CREATE: '/create', - LEARN: '/learn', - DREAM: [ - { - text: 'Ideaspace', - href: '/ideaspace', - hasUnderline: true, - }, - { - text: 'Submit an idea', - href: '/ideaspace/submit', - }, - { - text: 'Help existing idea', - href: '/ideaspace/browse', - }, - ], - 'SUPPORT US': '/support-us', - JOIN: '/join', +const DropdownMenu = ({ trigger, items = projectItems }) => { + const [isOpen, setIsOpen] = React.useState(false); + const dropdownRef = React.useRef(null); + + React.useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen]); + + return ( +
    + + + {isOpen && ( +
    +
    + {items.map((item, index) => ( + + +

    + {item.title} +

    +

    {item.description}

    +
    + + ))} +
    +
    + )} +
    + ); }; -export const accountOptions = [ - { text: 'My Profile', href: '/users/me' }, - { - text: 'my ideas dashboard', - href: '/ideaspace/dashboard', - hasUnderline: true, - }, -]; +const MobileDropdown = ({ title, items }) => { + const [isOpen, setIsOpen] = React.useState(false); + const dropdownRef = React.useRef(null); + + React.useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen]); + + return ( +
    + -// type NavigationProps = VariantProps; +
    + {items.map((item, index) => ( + + {item.label} + + ))} +
    +
    + ); +}; const Navigation = () => { - const [isSidebarExpanded, setIsSidebarExpanded] = React.useState(false); - const { $wrapper, $logoContainer } = NavigationStyles(); + const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false); const { userData, isAuthenticated } = useUserDataContext(); return ( - -
    + +
    + + +
    + +
    +
    + +
    +
    + + + + About Us + + + Resources + + + Donate + + + {!isAuthenticated ? ( +
    + + +
    + ) : ( +
    +
    + Profile + {userData.name} +
    + + + + Profile + + + + + + Idea Dashboard + + + +
    + )} +
    +
    + ); }; -Navigation.displayName = 'Navigation'; export default Navigation; diff --git a/packages/UI/src/components/organisms/Navigation/NotificationPopover/NotificationPopover.tsx b/packages/UI/src/components/organisms/Navigation/NotificationPopover/NotificationPopover.tsx index 521fcc826..b1f10a9c4 100644 --- a/packages/UI/src/components/organisms/Navigation/NotificationPopover/NotificationPopover.tsx +++ b/packages/UI/src/components/organisms/Navigation/NotificationPopover/NotificationPopover.tsx @@ -42,76 +42,83 @@ export default function NotificationPopover() { return ( - -
    -
    - - notification +
    +
    + + Notifications - + {/* {notifications.length > 0 && ( + + )} */}
    -
      - {notifications.map((n, i) => { - const { readDateTime, createdDateTime } = n.attributes; - const { action, entityName, eventUser, content, entityId } = - n.attributes.event.data.attributes; - const { username, discord_avatar } = eventUser.data.attributes; - return ( - + {notifications.length > 0 ? ( + notifications.map((n, i) => { + const { readDateTime, createdDateTime } = n.attributes; + const { action, entityName, eventUser, content, entityId } = + n.attributes.event.data.attributes; + const { username, discord_avatar } = eventUser.data.attributes; + return ( + + ); + }) + ) : ( +
      + - ); - })} -
    -
    +

    + No notifications yet. We'll notify you when something + important happens! +

    +
    + )} +
    + + {/* +
    */}
    diff --git a/production/kustomization.yaml b/production/kustomization.yaml index e23620d6a..7d3c1a351 100644 --- a/production/kustomization.yaml +++ b/production/kustomization.yaml @@ -7,4 +7,4 @@ bases: images: - name: devlaunchers/platform-website newName: devlaunchers/platform-website - newTag: 2.20.1 # {"$imagepolicy": "platform-website:platform-website:tag"} + newTag: 2.21.0 # {"$imagepolicy": "platform-website:platform-website:tag"} diff --git a/staging/kustomization.yaml b/staging/kustomization.yaml index 407d66515..57ec97020 100644 --- a/staging/kustomization.yaml +++ b/staging/kustomization.yaml @@ -6,4 +6,4 @@ resources: images: - name: devlaunchers/platform-website newName: devlaunchers/platform-website - newTag: "3f9b9f4-202412161933" # {"$imagepolicy": "platform-website-staging:platform-website:tag"} + newTag: '3e3d1b6-202501160039' # {"$imagepolicy": "platform-website-staging:platform-website:tag"} diff --git a/yarn.lock b/yarn.lock index f371aa905..378c93d12 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2715,8 +2715,10 @@ __metadata: babel-plugin-styled-components: 2.0.7 camelcase: 7.0.0 chromatic: ^6.7.3 + class-variance-authority: 0.7.1 clsx: 2.0.0 constate: ^3.3.2 + embla-carousel-react: 8.5.1 eslint: 8.21.0 eslint-config-prettier: 8.5.0 eslint-plugin-import: 2.26.0 @@ -15252,6 +15254,15 @@ __metadata: languageName: node linkType: hard +"class-variance-authority@npm:0.7.1": + version: 0.7.1 + resolution: "class-variance-authority@npm:0.7.1" + dependencies: + clsx: ^2.1.1 + checksum: e05ba26ef9ec38f7c675047ce366b067d60af6c954dba08f7802af19a9460a534ae752d8fe1294fff99d0fa94a669b16ccebd87e8a20f637c0736cf2751dd2c5 + languageName: node + linkType: hard + "classnames@npm:^2.2.5, classnames@npm:^2.2.6, classnames@npm:^2.3.2": version: 2.3.2 resolution: "classnames@npm:2.3.2" @@ -15458,6 +15469,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: acd3e1ab9d8a433ecb3cc2f6a05ab95fe50b4a3cfc5ba47abb6cbf3754585fcb87b84e90c822a1f256c4198e3b41c7f6c391577ffc8678ad587fc0976b24fd57 + languageName: node + linkType: hard + "cmd-shim@npm:^5.0.0": version: 5.0.0 resolution: "cmd-shim@npm:5.0.0" @@ -18087,6 +18105,34 @@ __metadata: languageName: node linkType: hard +"embla-carousel-react@npm:8.5.1": + version: 8.5.1 + resolution: "embla-carousel-react@npm:8.5.1" + dependencies: + embla-carousel: 8.5.1 + embla-carousel-reactive-utils: 8.5.1 + peerDependencies: + react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + checksum: 32881deb6870355fb360fe3e209293157ac464baca741ff3deb4a729473cf9107b66bcf8b50acd8a37ba36b76e0d61fedd77b95938a1f7ffa4af6fb04aa3dbdc + languageName: node + linkType: hard + +"embla-carousel-reactive-utils@npm:8.5.1": + version: 8.5.1 + resolution: "embla-carousel-reactive-utils@npm:8.5.1" + peerDependencies: + embla-carousel: 8.5.1 + checksum: 1a8a39da789eec3ea9d87031e411de2abb8c2a110b2dfde8d83569954a5d07e4067cfbcfc82381e964458c41b7bcd9f6f64c40f51fc09135aba1fc856804841a + languageName: node + linkType: hard + +"embla-carousel@npm:8.5.1": + version: 8.5.1 + resolution: "embla-carousel@npm:8.5.1" + checksum: dd3e9b23accc6cfe5aecc86ba12f3687be3581580a9ef694fd255fdd76f2a376e28545e84613ff7bbeab6ca2215d276b962a0c7a602601221f2050ab5c40658e + languageName: node + linkType: hard + "emittery@npm:^0.10.2": version: 0.10.2 resolution: "emittery@npm:0.10.2"