diff --git a/.gitignore b/.gitignore index 7ea0b16d..8859a42a 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,9 @@ typings/ # nuxt.js build output .nuxt +# Nuxt 3 build output +.output + # Nuxt generate dist diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..fbf6aa28 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,161 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is the Lansing Codes community website - a resource hub for coders and tech enthusiasts in Lansing, Michigan. The site features a calendar of tech events, meetup groups, local resources, sponsor information, and community engagement tools. + +## Tech Stack + +- **Framework**: Nuxt 3 (Vue 3) +- **Styling**: Tailwind CSS with custom SCSS +- **Database**: Firebase Firestore (read-only production data by default in development) +- **Data Management**: VueFire for Firestore integration, migrating from legacy Vuexfire +- **Icons**: FontAwesome via `@fortawesome/vue-fontawesome` +- **Testing**: Playwright for UI testing +- **Node Version**: >= 22.18.0 + +## Architecture + +### Migration Status (Nuxt 2 → Nuxt 3) + +This codebase is **currently being migrated** from Nuxt 2 to Nuxt 3: + +- **Legacy patterns**: `store/` directory still contains Vuex stores using `vuexfire` (old pattern) +- **New patterns**: `composables/` directory contains Composition API composables using `vuefire` (new pattern) +- Both patterns exist side-by-side during the migration +- Prefer using composables for new features; gradually migrate store usage to composables + +### Data Flow + +**Firebase Integration**: +- Firestore database connection configured in [utils/firestore.js](utils/firestore.js) +- Firebase app initialized in [plugins/vuefire.client.js](plugins/vuefire.client.js) +- Default environment points to production read-only data +- Custom Firebase config can be provided via `FIREBASE_WEB_CONFIG` environment variable + +**Data Loading Patterns**: +1. **New (Nuxt 3)**: Composables in `composables/` directory + - `useEvents()`: Loads upcoming events from Firestore + - `useGroups()`: Loads meetup groups + - `useSponsors()`: Loads sponsor information + - Use VueFire's `useCollection()` for reactive Firestore queries + +2. **Legacy (Nuxt 2)**: Vuex stores in `store/` directory + - Still present but being phased out + - Use firestoreAction from vuexfire + +**Event Filtering**: +- Events are filtered by date range: today through the end of the configured number of weeks +- Maximum calendar weeks configured in [config/max-calendar-weeks.js](config/max-calendar-weeks.js) +- Uses `date-fns` for date manipulation + +### Component Structure + +**Page Structure**: +- Single layout: [layouts/default.vue](layouts/default.vue) with header (Navigation) and footer +- Homepage ([pages/index.vue](pages/index.vue)) is composed of section components: + - Welcome + - Events (calendar and list views) + - Meetups + - Resources + - Sponsors + - Newsletter + +**Component Organization**: +- Section components in `components/` directory (e.g., `events.vue`, `meetups.vue`) +- Reusable sub-components (e.g., `event-calendar.vue`, `event-list.vue`, `card--event.vue`) +- Logo components with size variants (`logo--small.vue`, `logo--medium.vue`, etc.) + +### Styling + +- Global styles in [layouts/default.vue](layouts/default.vue) +- Tailwind configuration in [tailwind.config.js](tailwind.config.js) +- Main SCSS entry point: `assets/scss/tailwind.scss` +- Custom CSS classes: + - `.lc-bg-ltr-gradient`: Left-to-right blue gradient + - `.lc-bg-right-triangle`, `.lc-bg-left-triangle`, `.lc-bg-down-triangle`: Triangle background patterns + - `.sr-only`: Screen reader only content + +### Configuration + +- **SSR**: Disabled (`ssr: false` in nuxt.config.ts) +- **Firebase config**: Hardcoded in nuxt.config.ts under `runtimeConfig.public.firebaseConfig` +- **Resources**: Static resource data in [config/resources.js](config/resources.js) +- **Fonts**: External fonts loaded from CDN (Lansing Codes webfont, Font Mfizz, Montserrat) + +## Common Commands + +### Development +```sh +npm run dev +``` +Runs dev server at http://localhost:3000 with hot reload + +### Build +```sh +npm run build +``` +Builds production site (output in `.output/` directory) + +### Generate Static Site +```sh +npm run generate +``` +Generates static files for deployment + +### Preview Production Build +```sh +npm run preview +``` +Previews the production build locally + +### Linting +```sh +npm run lint # Check for linting errors +npm run lintfix # Auto-fix linting errors +``` +Linting runs automatically on `git commit` via the `precommit` hook + +### Testing + + +## Git Workflow + +- **Main branch**: `main` - snapshot of latest in-flight release +- **Production branch**: `production` - live production code +- **Development**: Create feature branches from `main` +- **Deployment**: PRs merge to `main` → auto-deploy to staging → manual promotion to `production` + +### Contributors + +When contributing, add your name to the `contributors` array in [package.json](package.json) + +### Branch Promotion to Production +```sh +git fetch origin && git push --force origin origin/main:production +``` + +## Deployment + +- **Staging**: Auto-deployed to Netlify on merge to `main` + - URL: lansingcodes-staging.netlify.com + - All branches get preview deployments +- **Production**: Auto-deployed to Netlify when `production` branch is updated + - URL: www.lansing.codes +- **Build command**: `npm run build` +- **Deploy directory**: `/dist` (Nuxt 2) or `/.output` (Nuxt 3) + +## Utilities + +Located in `utils/` directory: +- `clean-event-description.js`: Sanitizes event description HTML +- `format-readable-date-time.js`: Formats timestamps into readable strings +- `group-for-event.js`: Matches events to their parent meetup groups +- `simplified-name.js`: Normalizes names for comparison +- `firestore.js`: Firebase/Firestore initialization and exports + +## Code of Conduct + +All contributors must follow the [Code of Conduct](https://www.lansing.codes/code-of-conduct/). Treat all participants with respect. diff --git a/app.vue b/app.vue new file mode 100644 index 00000000..f8eacfa7 --- /dev/null +++ b/app.vue @@ -0,0 +1,5 @@ + diff --git a/app/router.scrollBehavior.js b/app/router.scrollBehavior.js index 0ada7085..7066b2a1 100644 --- a/app/router.scrollBehavior.js +++ b/app/router.scrollBehavior.js @@ -2,7 +2,7 @@ // https://nuxtjs.org/api/configuration-router#scrollbehavior // Without this explicitly provided, though, using the back button in // Chrome does not work when navigating different sections of the page. -export default function (to, from, savedPosition) { +export default function(to, from, savedPosition) { // if the returned position is falsy or an empty object, // will retain current scroll position. let position = false @@ -11,7 +11,7 @@ export default function (to, from, savedPosition) { if (to.matched.length < 2) { // scroll to the top of the page position = { x: 0, y: 0 } - } else if (to.matched.some((r) => r.components.default.options.scrollToTop)) { + } else if (to.matched.some(r => r.components.default.options.scrollToTop)) { // if one of the children has scrollToTop option set to true position = { x: 0, y: 0 } } @@ -21,7 +21,7 @@ export default function (to, from, savedPosition) { position = savedPosition } - return new Promise((resolve) => { + return new Promise(resolve => { // wait for the out transition to complete (if necessary) window.$nuxt.$once('triggerScroll', () => { // coords will be used if no selector is provided, diff --git a/assets/scss/tailwind.scss b/assets/scss/tailwind.scss index 5c3a43b0..237528f9 100644 --- a/assets/scss/tailwind.scss +++ b/assets/scss/tailwind.scss @@ -4,9 +4,9 @@ * * If using `postcss-import`, use this import instead: * - * @import "tailwindcss/preflight"; + * @import "tailwindcss/base"; */ -@tailwind preflight; +@tailwind base; /** * This injects any component classes registered by plugins. diff --git a/assets/scss/utilities/transforms.scss b/assets/scss/utilities/transforms.scss index 9d0698ab..3b469105 100644 --- a/assets/scss/utilities/transforms.scss +++ b/assets/scss/utilities/transforms.scss @@ -22,14 +22,26 @@ transform: translateY(100vh); } -@variants active, hover { +@layer utilities { .slide-down-px { transform: translateY(1px); transition: all ease-in-out .1s; } + .active\:slide-down-px:active, + .hover\:slide-down-px:hover { + transform: translateY(1px); + transition: all ease-in-out .1s; + } + .slide-up-px { transform: translateY(-1px); transition: all ease-in-out .1s; } + + .active\:slide-up-px:active, + .hover\:slide-up-px:hover { + transform: translateY(-1px); + transition: all ease-in-out .1s; + } } diff --git a/components/calendar--event.vue b/components/calendar--event.vue index 3222271e..963ee9b1 100644 --- a/components/calendar--event.vue +++ b/components/calendar--event.vue @@ -1,157 +1,141 @@ + - diff --git a/components/card--event.vue b/components/card--event.vue index b920a510..23937061 100644 --- a/components/card--event.vue +++ b/components/card--event.vue @@ -8,6 +8,7 @@ >
-
{{ group.name }}
+
{{ group.name }}
{{ formatReadableDateTime(event.startTime) }}
@@ -32,9 +33,13 @@
-
+

{{ venue }}

-
+
{{ address }}
@@ -48,41 +53,31 @@ - diff --git a/composables/useEvents.js b/composables/useEvents.js new file mode 100644 index 00000000..49bcc179 --- /dev/null +++ b/composables/useEvents.js @@ -0,0 +1,23 @@ +import { collection, query, where, orderBy } from 'firebase/firestore' +import { useCollection } from 'vuefire' +import { db } from '~/utils/firestore' +import { startOfDay, addWeeks, endOfDay, endOfWeek } from 'date-fns' +import weeksAvailable from '~/config/max-calendar-weeks' + +export const useEvents = () => { + const startDate = startOfDay(Date.now()) + const endDate = endOfDay(endOfWeek(addWeeks(startDate, weeksAvailable - 1))) + + const eventsQuery = query( + collection(db, 'events'), + where('startTime', '>=', startDate.getTime()), + where('startTime', '<=', endDate.getTime()), + orderBy('startTime', 'asc') + ) + + const upcoming = useCollection(eventsQuery, { ssrKey: 'events' }) + + return { + upcoming + } +} diff --git a/composables/useGroups.js b/composables/useGroups.js new file mode 100644 index 00000000..66d7e37c --- /dev/null +++ b/composables/useGroups.js @@ -0,0 +1,11 @@ +import { collection } from 'firebase/firestore' +import { useCollection } from 'vuefire' +import { db } from '~/utils/firestore' + +export const useGroups = () => { + const all = useCollection(collection(db, 'groups'), { ssrKey: 'groups' }) + + return { + all + } +} diff --git a/composables/useSponsors.js b/composables/useSponsors.js new file mode 100644 index 00000000..55cd119b --- /dev/null +++ b/composables/useSponsors.js @@ -0,0 +1,11 @@ +import { collection } from 'firebase/firestore' +import { useCollection } from 'vuefire' +import { db } from '~/utils/firestore' + +export const useSponsors = () => { + const all = useCollection(collection(db, 'sponsors'), { ssrKey: 'sponsors' }) + + return { + all + } +} diff --git a/config/resources.js b/config/resources.js index aa71e512..019fc53f 100644 --- a/config/resources.js +++ b/config/resources.js @@ -6,7 +6,7 @@ export default [ Use professional tools and processes to learn HTML, CSS, JavaScript, and more. Get help from local mentors. No prior experience required. Visit the website to find out when the next lab will be and to apply. - `, + ` }, { title: 'freeCodeCamp', @@ -16,6 +16,6 @@ export default [ contributing to open source projects used by non-profits. Learn to code by completing coding challenges and building projects. freeCodeCamp is a donor supported non-profit and every aspect is 100% free. - `, - }, + ` + } ] diff --git a/layouts/default.vue b/layouts/default.vue index 8a378a3b..e2e3ecf7 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -1,42 +1,37 @@ - diff --git a/pages/index.vue b/pages/index.vue index e2fc7829..0193b862 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -9,30 +9,11 @@
- diff --git a/pages/sponsors-signup.vue b/pages/sponsors-signup.vue index ebbd991a..994df02a 100644 --- a/pages/sponsors-signup.vue +++ b/pages/sponsors-signup.vue @@ -18,9 +18,13 @@
-
+
-
- - -
How would you like to contribute?
@@ -57,14 +73,16 @@ value="monthly-lansing-codes" type="radio" name="contribute-options" - /> -
-
-
-
-
-
-
@@ -191,8 +221,10 @@ value="invoice-yes" type="radio" name="invoice-options" - /> - + > +