├── .dockerignore ├── .env.example ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── feedback.md └── workflows │ ├── cypress_test.yaml │ ├── docker_push.yaml │ ├── jest_test.yaml │ ├── lint_format.yaml │ └── rebase.yaml ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc.js ├── .prettierignore ├── Dockerfile ├── LICENSE ├── README.md ├── cypress.json ├── cypress ├── fixtures │ ├── happening.json │ └── users.json ├── integration │ ├── entry-box.spec.ts │ ├── happening-registration.spec.ts │ ├── nav.spec.ts │ └── registration-deletion.spec.ts └── tsconfig.json ├── docker-compose.yaml ├── jest.setup.js ├── next-env.d.ts ├── next.config.js ├── package.json ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── bedpres.png ├── bekk.png ├── browserconfig.xml ├── christmas-icons │ └── santa_hat.svg ├── echo-logo-text-only-white-no-padding-bottom.png ├── echo-logo-white.png ├── echo-logo.png ├── event.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── halloween-icons │ ├── ghost.svg │ ├── hat.svg │ ├── pumpkin.svg │ └── skull.svg ├── maskable-icon-192x192.png ├── maskable-icon-512x512.png ├── mstile-150x150.png ├── post.png ├── powered-by-vercel.svg ├── safari-pinned-tab.svg ├── sanity-logo.svg ├── site.webmanifest └── static │ └── valg.md ├── src ├── components │ ├── __tests__ │ │ ├── footer.test.tsx │ │ ├── happening-meta-info.test.tsx │ │ ├── header.test.tsx │ │ ├── info-panels.test.tsx │ │ ├── layout.test.tsx │ │ ├── navbar.test.tsx │ │ ├── testing-utils.ts │ │ └── testing-wrapper.tsx │ ├── animated-icons.tsx │ ├── article.tsx │ ├── bedpres-preview.tsx │ ├── button-link.tsx │ ├── button.tsx │ ├── calendar-popup.tsx │ ├── color-mode-button.tsx │ ├── countdown.tsx │ ├── entry-box.tsx │ ├── entry-list.tsx │ ├── entry-overview.tsx │ ├── error-box.tsx │ ├── event-preview.tsx │ ├── footer.tsx │ ├── form-question.tsx │ ├── form-term.tsx │ ├── happening-key-info.tsx │ ├── happening-meta-info.tsx │ ├── header-logo.tsx │ ├── header.tsx │ ├── icon-text.tsx │ ├── info-panels.tsx │ ├── job-advert-overview.tsx │ ├── job-advert-preview.tsx │ ├── layout.tsx │ ├── logo-accesory.tsx │ ├── member-profile.tsx │ ├── minute-list.tsx │ ├── nav-link.tsx │ ├── navbar.tsx │ ├── post-list.tsx │ ├── post-preview.tsx │ ├── profile-info.tsx │ ├── registration-form.tsx │ ├── registration-row.tsx │ ├── section.tsx │ ├── seo.tsx │ ├── sidebar-wrapper.tsx │ ├── sidebar.tsx │ └── student-group-view.tsx ├── declarations.d.ts ├── lib │ ├── api │ │ ├── api.ts │ │ ├── banner.ts │ │ ├── decoders.ts │ │ ├── errors.ts │ │ ├── happening.ts │ │ ├── index.ts │ │ ├── job-advert.ts │ │ ├── minute.ts │ │ ├── post.ts │ │ ├── registration.ts │ │ ├── static-info.tsx │ │ ├── student-group.ts │ │ ├── types.ts │ │ └── user.ts │ ├── generate-rss-feed.ts │ ├── hooks │ │ ├── index.ts │ │ └── use-countdown.ts │ └── utils.ts ├── markdown.ts ├── pages │ ├── 404.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── api │ │ ├── auth │ │ │ └── [...nextauth].ts │ │ └── user │ │ │ └── index.ts │ ├── bedpres │ │ └── index.tsx │ ├── event │ │ ├── [slug].tsx │ │ └── index.tsx │ ├── happenings-overview │ │ └── index.tsx │ ├── index.tsx │ ├── job │ │ ├── [slug].tsx │ │ └── index.tsx │ ├── om-echo │ │ ├── [slug].tsx │ │ ├── moetereferat │ │ │ └── index.tsx │ │ └── studentgrupper │ │ │ └── [slug].tsx │ ├── posts │ │ ├── [slug].tsx │ │ └── index.tsx │ ├── profile.tsx │ ├── registration │ │ └── [slug].tsx │ └── valg │ │ └── index.tsx └── styles │ ├── fonts.tsx │ ├── theme.ts │ └── themes │ ├── christmas-theme.ts │ ├── halloween-theme.ts │ ├── index.ts │ └── main-theme.ts ├── tsconfig.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .next/ 3 | .git/ 4 | .husky/ 5 | .cache/ 6 | .github/ 7 | 8 | LICENSE 9 | README.md 10 | docker-compose* 11 | Dockerfile 12 | .prettierignore 13 | jest* 14 | public/rss.xml 15 | 16 | cypress/videos/ 17 | cypress/screenshots/ 18 | cypress/downloads/ 19 | cypress/fixtures/example.json 20 | cypress/plugins/ 21 | cypress/support/ 22 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Needed for frontend. 2 | # This specifies what key the frontend uses 3 | # to try to access authorized endpoints in the backend. 4 | # Must match whatever key the backend you are sending requests 5 | # to uses. 6 | ADMIN_KEY=key_here 7 | 8 | # Needed for Feide authentication. 9 | FEIDE_CLIENT_ID=client_id_here 10 | FEIDE_CLIENT_SECRET=client_secret_here 11 | NEXTAUTH_URL=http://localhost:3000 12 | NEXTAUTH_SECRET=secret_here 13 | 14 | # Optional. 15 | # 16 | # For normal use: 17 | # SANITY_DATASET=production 18 | # 19 | # For Cypress testing: 20 | # SANITY_DATASET=production 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug-rapport 3 | about: Har du opplevd en bug? Opprett en issue her. 4 | title: '[Bug]' 5 | labels: 'bug :bug:' 6 | assignees: bakseter, boaanes 7 | --- 8 | 9 | **Beskriv buggen** 10 | En konsis beskrivelse av hva som skjer. 11 | 12 | **Hvordan skjer buggen?** 13 | Steg for å reprodusere: 14 | 15 | 1. Gå til '...' 16 | 2. Klikk på '....' 17 | 3. Scroll ned til '....' 18 | 4. Se buggen 19 | 20 | **Forventet oppførsel** 21 | En konsis beskrivelse av hva du fortventer skal skje. 22 | 23 | **Screenshots** 24 | Legg gjerne med skjermbilder/gifer. 25 | 26 | **Detaljer:** 27 | 28 | - OS: [e.g. iOS] 29 | - Nettleser [e.g. chrome, safari] 30 | 31 | **Eventuelle greier** 32 | Legg til eventuelle småting her. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Foreslag til funksjonalitet 3 | about: Har du en idé til nettsiden? 4 | title: '[Funksjonalitet]' 5 | labels: 'feature :sparkles:' 6 | assignees: bakseter, boaanes 7 | --- 8 | 9 | **Hva ønsker du av ny funksjonalitet?** 10 | En konsis beskrivelse av hva du vil ha/hva problemet er. F.eks. "Det plager meg at [...]" 11 | 12 | **Beskriv løsningen(e) du ser for deg** 13 | En konsis beskrivelse av hva løsningen(e) kan være. 14 | 15 | **Eventuelle ting** 16 | Legg til eventuelle greier her. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feedback.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Tilbakemelding 3 | about: Hjelp oss bli bedre 4 | title: '[Tilbakemelding]' 5 | labels: 'feedback :scroll:' 6 | assignees: bakseter, boaanes 7 | --- 8 | 9 | **Tilbakemelding** 10 | Beskriv tilbakemeldingen din her. 11 | 12 | **Eventuelt** 13 | Legg til eventuelle småting her. 14 | -------------------------------------------------------------------------------- /.github/workflows/cypress_test.yaml: -------------------------------------------------------------------------------- 1 | name: Cypress 2 | on: 3 | pull_request: 4 | branches: [master] 5 | 6 | env: 7 | REGISTRY: ghcr.io 8 | BACKEND_TAG: latest-prod 9 | 10 | permissions: 11 | packages: write 12 | actions: read 13 | 14 | jobs: 15 | cypress_tests: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout frontend repository 19 | uses: actions/checkout@v3 20 | 21 | - name: Login to GitHub Container Registry 22 | uses: docker/login-action@v1 23 | with: 24 | registry: ${{ env.REGISTRY }} 25 | username: ${{ github.actor }} 26 | password: ${{ secrets.GITHUB_TOKEN }} 27 | 28 | - name: Build frontend with cache & push 29 | run: | 30 | docker build \ 31 | --cache-from "$REGISTRY/$GITHUB_REPOSITORY" \ 32 | -t "$REGISTRY/$GITHUB_REPOSITORY:$GITHUB_SHA" \ 33 | --build-arg BUILDKIT_INLINE_CACHE=1 \ 34 | --build-arg SANITY_DATASET=$SANITY_DATASET \ 35 | . 36 | docker push "$REGISTRY/$GITHUB_REPOSITORY" --all-tags 37 | env: 38 | DOCKER_BUILDKIT: 1 39 | REGISTRY: ${{ env.REGISTRY }} 40 | SANITY_DATASET: ${{ secrets.SANITY_DATASET }} 41 | 42 | - name: Pull backend image 43 | run: docker pull "$REGISTRY/$BACKEND_REPOSITORY:$TAG" 44 | env: 45 | REGISTRY: ${{ env.REGISTRY }} 46 | BACKEND_REPOSITORY: echo-webkom/echo-web-backend 47 | TAG: ${{ env.BACKEND_TAG }} 48 | 49 | - name: Run Cypress end-to-end tests against backend 50 | run: docker compose up --exit-code-from=frontend --attach=frontend 51 | env: 52 | REGISTRY: ${{ env.REGISTRY }} 53 | TAG: ${{ env.BACKEND_TAG }} 54 | SANITY_DATASET: ${{ secrets.SANITY_DATASET }} 55 | ADMIN_KEY: admin-passord 56 | NEXTAUTH_SECRET: very-secret-string-123 57 | -------------------------------------------------------------------------------- /.github/workflows/docker_push.yaml: -------------------------------------------------------------------------------- 1 | name: Docker push image 2 | on: 3 | push: 4 | branches: [master] 5 | 6 | env: 7 | REGISTRY: ghcr.io 8 | 9 | permissions: 10 | packages: write 11 | actions: read 12 | 13 | jobs: 14 | docker_push: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout frontend repository 18 | uses: actions/checkout@v3 19 | 20 | - name: Login to GitHub Container Registry 21 | uses: docker/login-action@v1 22 | with: 23 | registry: ${{ env.REGISTRY }} 24 | username: ${{ github.actor }} 25 | password: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | - name: Build frontend with cache & push 28 | run: | 29 | docker build \ 30 | --cache-from "$REGISTRY/$GITHUB_REPOSITORY" \ 31 | -t "$REGISTRY/$GITHUB_REPOSITORY:$TAG" \ 32 | --build-arg BUILDKIT_INLINE_CACHE=1 \ 33 | --build-arg SANITY_DATASET=$SANITY_DATASET \ 34 | . 35 | docker push "$REGISTRY/$GITHUB_REPOSITORY" --all-tags 36 | env: 37 | DOCKER_BUILDKIT: 1 38 | REGISTRY: ${{ env.REGISTRY }} 39 | SANITY_DATASET: ${{ secrets.SANITY_DATASET }} 40 | TAG: latest 41 | -------------------------------------------------------------------------------- /.github/workflows/jest_test.yaml: -------------------------------------------------------------------------------- 1 | name: Jest 2 | on: 3 | pull_request: 4 | branches: [master] 5 | 6 | jobs: 7 | jest_tests: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout repository 11 | uses: actions/checkout@v3 12 | 13 | - name: Cache dependencies 14 | uses: actions/cache@v3 15 | with: 16 | path: '**/node_modules' 17 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 18 | 19 | - name: Install dependencies 20 | run: yarn --frozen-lockfile --ignore-scripts 21 | 22 | - name: Run API & component tests 23 | run: yarn test 24 | -------------------------------------------------------------------------------- /.github/workflows/lint_format.yaml: -------------------------------------------------------------------------------- 1 | name: Lint & format 2 | on: 3 | pull_request: 4 | branches: [master] 5 | 6 | jobs: 7 | lint_format: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout repository 11 | uses: actions/checkout@v3 12 | 13 | - name: Cache dependencies 14 | uses: actions/cache@v3 15 | with: 16 | path: '**/node_modules' 17 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 18 | 19 | - name: Install dependencies 20 | run: yarn --frozen-lockfile --ignore-scripts 21 | 22 | - name: Run linter 23 | run: yarn next lint 24 | 25 | - name: Run prettier check 26 | run: | 27 | yarn prettier -c "**/*.{js,jsx,ts,tsx,json,md}" 28 | yarn prettier -c --tab-width=2 "**/*.{yaml,yml}" 29 | -------------------------------------------------------------------------------- /.github/workflows/rebase.yaml: -------------------------------------------------------------------------------- 1 | name: Automatic rebase 2 | on: 3 | issue_comment: 4 | types: [created] 5 | 6 | jobs: 7 | rebase: 8 | name: Rebase 9 | if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase') 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout the latest code 13 | uses: actions/checkout@v3 14 | with: 15 | token: ${{ secrets.REPO_PAT }} 16 | fetch-depth: 0 17 | 18 | - name: Automatic rebase 19 | uses: cirrus-actions/rebase@1.6 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.REPO_PAT }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | public/rss.xml 3 | public/sw.js* 4 | public/workbox-*.js* 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Cypress 20 | cypress/videos/ 21 | cypress/screenshots/ 22 | cypress/downloads/ 23 | cypress/fixtures/example.json 24 | cypress/plugins/ 25 | cypress/support/ 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | 33 | # nyc test coverage 34 | .nyc_output 35 | 36 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 37 | .grunt 38 | 39 | # Bower dependency directory (https://bower.io/) 40 | bower_components 41 | 42 | # node-waf configuration 43 | .lock-wscript 44 | 45 | # Compiled binary addons (http://nodejs.org/api/addons.html) 46 | build/Release 47 | 48 | # Dependency directories 49 | node_modules/ 50 | jspm_packages/ 51 | 52 | # Typescript v1 declaration files 53 | typings/ 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional REPL history 62 | .node_repl_history 63 | 64 | # Output of 'npm pack' 65 | *.tgz 66 | 67 | # dotenv environment variable files 68 | .env* 69 | 70 | # example dotenv 71 | !.env.example 72 | 73 | # next 74 | .next 75 | 76 | # Mac files 77 | .DS_Store 78 | 79 | # Yarn 80 | yarn-error.log 81 | .pnp/ 82 | .pnp.js 83 | # Yarn Integrity file 84 | .yarn-integrity 85 | 86 | #vs code 87 | .vscode/ 88 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{js,jsx,ts,tsx}': (filenames) => 3 | `next lint --fix --file ${filenames.map((file) => file.split(process.cwd())[1]).join(' --file ')}`, 4 | '*.{js,jsx,ts,tsx,json,md}': 'prettier --write', 5 | '*.{yaml,yml}': 'prettier --write --tab-width=2', 6 | }; 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .next/ 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # ONLY FOR DEVELOPMENT AND TESTING USE 2 | 3 | # Install dependencies with yarn. 4 | # Node 14 is the version Vercel uses. 5 | FROM node:14-alpine as deps 6 | 7 | WORKDIR /opt/build 8 | COPY package.json yarn.lock ./ 9 | 10 | RUN yarn --frozen-lockfile 11 | 12 | 13 | # Build with Next (no default command). 14 | FROM cypress/base:latest as build 15 | 16 | ARG SANITY_DATASET 17 | 18 | WORKDIR /opt/build 19 | COPY --from=deps /opt/build/node_modules ./node_modules/ 20 | COPY --from=deps /root/.cache /root/.cache/ 21 | # NB! Copies any .env files as well. 22 | COPY . . 23 | 24 | RUN yarn build 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # echo web frontend 2 | 3 | [![Jest](https://github.com/echo-webkom/echo-web-frontend/actions/workflows/jest_test.yaml/badge.svg)](https://github.com/echo-webkom/echo-web-frontend/actions/workflows/jest_test.yaml) 4 | [![Cypress](https://github.com/echo-webkom/echo-web-frontend/actions/workflows/cypress_test.yaml/badge.svg)](https://github.com/echo-webkom/echo-web-frontend/actions/workflows/cypress_test.yaml) 5 | 6 | 7 | Powered by Vercel 8 | 9 | 10 | Frontend til nettsiden til **echo – Linjeforeningen for informatikk** ved Universitetet i Bergen. 11 | 12 | Utviklet av frivillige informatikkstudenter fra undergruppen **echo Webkom**. 13 | 14 | ## Tilbakemeldinger 15 | 16 | Har du noen tilbakemeldinger til nettsiden? 17 | Vi jobber hele tiden med å forbedre den, 18 | og setter stor pris på om du sier ifra om noe er feil, 19 | eller du har idéer til nye endringer! 20 | 21 | Fyll gjerne ut skjemaet [her](https://forms.gle/r9LNMFjanUNP7Gph9), 22 | eller send oss en mail på [webkom-styret@echo.uib.no](mailto:webkom-styret@echo.uib.no). 23 | 24 | ## Oppsett for utviklere 25 | 26 | **1. Klon Git-repoet.** 27 | 28 | git clone git@github.com:echo-webkom/echo-web-frontend 29 | 30 | **2. Naviger til riktig mappe.** 31 | 32 | cd echo-web-frontend 33 | 34 | **3. Installer dependencies (du trenger [yarn](https://classic.yarnpkg.com/en/docs/install) for dette).** 35 | 36 | yarn 37 | 38 | **4. Kopier innholdet i `.env.example` til en fil med navn `.env` (og evt. fyll inn verdier for feltene).** 39 | 40 | cp .env.example .env 41 | 42 | **5. Start en lokal server.** 43 | 44 | yarn dev 45 | 46 | Gå til `localhost:3000` i en nettleser for å se nettsiden. 47 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "defaultCommandTimeout": 5000, 4 | "testFiles": ["entry-box.spec.ts", "nav.spec.ts", "happening-registration.spec.ts", "registration-deletion.spec.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /cypress/fixtures/happening.json: -------------------------------------------------------------------------------- 1 | { 2 | "happenings": [ 3 | { "slug": "fest-med-tilde", "type": "EVENT" }, 4 | { "slug": "bedriftspresentasjon-med-bekk", "type": "BEDPRES" } 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /cypress/fixtures/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "bachelorDegrees": ["DTEK", "DSIK", "DVIT", "BINF", "IMO"], 3 | "masterDegrees": ["INF", "PROG"], 4 | "validArmninfUser": { 5 | "degree": "ARMNINF", 6 | "degreeYear": 1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /cypress/integration/entry-box.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 2 | 3 | describe('Entry Box', () => { 4 | describe('720p res', () => { 5 | beforeEach(() => { 6 | cy.viewport(1280, 720); 7 | }); 8 | 9 | describe('Bedpres Entry Box', () => { 10 | it('Should link to bedpreses page', () => { 11 | const bedpresPage = '/bedpres'; 12 | 13 | cy.visit('/'); 14 | cy.get('[data-cy=entry-box-bedpres]').within(() => { 15 | cy.get(`[data-cy="${bedpresPage}"]`).click(); 16 | cy.url().should('include', bedpresPage); 17 | }); 18 | }); 19 | }); 20 | 21 | describe('Event Entry Box', () => { 22 | it('Should link to event page', () => { 23 | const eventPage = '/event'; 24 | 25 | cy.visit('/'); 26 | cy.get('[data-cy=entry-box-event]').within(() => { 27 | cy.get(`[data-cy="${eventPage}"]`).click(); 28 | cy.url().should('include', eventPage); 29 | }); 30 | }); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /cypress/integration/happening-registration.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 2 | 3 | import users from '../fixtures/users.json'; 4 | import { happenings } from '../fixtures/happening.json'; 5 | 6 | const checkSubmitRegistration = (degree: string, degreeYear: number) => { 7 | cy.get('[data-cy=reg-btn]').click(); 8 | cy.get('[data-cy=reg-form]').should('be.visible'); 9 | 10 | cy.get('input[name=email]').type(`${degree}${degreeYear}@test.com`); 11 | cy.get('input[name=firstName]').type('Test'); 12 | cy.get('input[name=lastName]').type('McTest'); 13 | cy.get('select[name=degree]').select(degree); 14 | cy.get('input[name=degreeYear]').check(degreeYear.toString(), { force: true }); 15 | cy.get('input[id=terms1]').check({ force: true }); 16 | cy.get('input[id=terms2]').check({ force: true }); 17 | cy.get('input[id=terms3]').check({ force: true }); 18 | 19 | cy.get('button[type=submit]').click(); 20 | 21 | cy.get('li[class=chakra-toast]').contains('Påmeldingen din er registrert!'); 22 | }; 23 | 24 | describe('Happening registration', () => { 25 | describe('720p res', () => { 26 | beforeEach(() => { 27 | cy.viewport(1280, 720); 28 | }); 29 | 30 | for (const { slug, type } of happenings) { 31 | context('Happening form registration', () => { 32 | beforeEach(() => { 33 | cy.visit(`/event/${slug}`); 34 | }); 35 | 36 | it('Popup form appears correctly', () => { 37 | cy.get('[data-cy=reg-btn]').click(); 38 | cy.get('[data-cy=reg-form]').should('be.visible'); 39 | 40 | cy.get('input[name=email]').should('be.visible'); 41 | cy.get('input[name=firstName]').should('be.visible'); 42 | cy.get('input[name=lastName]').should('be.visible'); 43 | cy.get('select[name=degree]').should('be.visible'); 44 | 45 | /* 46 | 47 | TODO: fix this. 48 | 49 | Chakra hides radio and checkbox input beneath another element, 50 | therefore it will never be visible. 51 | 52 | cy.get('input[name=degreeYear]').should('be.visible'); 53 | cy.get('input[id=terms1]').should('be.visible'); 54 | cy.get('input[id=terms2]').should('be.visible'); 55 | cy.get('input[id=terms3]').should('be.visible'); 56 | 57 | */ 58 | }); 59 | 60 | for (const b of users.bachelorDegrees) { 61 | for (const y of [1, 2, 3]) { 62 | it(`User can sign up with valid input (degree = ${b}, degreeYear = ${y}, type = ${type})`, () => { 63 | checkSubmitRegistration(b, y); 64 | }); 65 | } 66 | } 67 | 68 | for (const m of users.masterDegrees) { 69 | for (const y of [4, 5]) { 70 | it(`User can sign up with valid input (degree = ${m}, degreeYear = ${y}, type = ${type})`, () => { 71 | checkSubmitRegistration(m, y); 72 | }); 73 | } 74 | } 75 | 76 | it(`User can sign up with valid input (degree = ${users.validArmninfUser.degree}, degreeYear = ${users.validArmninfUser.degreeYear}, type = ${type})`, () => { 77 | checkSubmitRegistration(users.validArmninfUser.degree, users.validArmninfUser.degreeYear); 78 | }); 79 | }); 80 | } 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /cypress/integration/nav.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 2 | 3 | Cypress.on('uncaught:exception', (err) => { 4 | if (err.message.includes('loop limit exceeded')) { 5 | return false; 6 | } 7 | }); 8 | 9 | describe('Nav Menus', () => { 10 | describe('720p res', () => { 11 | beforeEach(() => { 12 | cy.viewport(1280, 720); 13 | }); 14 | 15 | describe('When visiting the home page', () => { 16 | it('Should visit homepage', () => { 17 | cy.visit('/'); 18 | }); 19 | 20 | describe('navbar', () => { 21 | it('Should navigate to om-echo page', () => { 22 | cy.get('[data-cy=nav-item]').contains('Om echo').click(); 23 | cy.url().should('include', '/om-echo/om-oss'); 24 | }); 25 | it('Should navigate to home page', () => { 26 | cy.get('[data-cy=nav-item]').contains('Hjem').click(); 27 | cy.url().should('eq', 'http://localhost:3000/'); 28 | }); 29 | }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /cypress/integration/registration-deletion.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 2 | import { happenings } from '../fixtures/happening.json'; 3 | 4 | const registrationRoute = 'registration'; 5 | 6 | describe('Happening registration', () => { 7 | describe('720p res', () => { 8 | beforeEach(() => { 9 | cy.viewport(1280, 720); 10 | }); 11 | 12 | for (const { slug } of happenings) { 13 | for (let rows = 20; rows > 0; rows--) { 14 | describe('Happening registration deletion', () => { 15 | beforeEach(() => { 16 | cy.visit(`/${registrationRoute}/${slug}`); 17 | }); 18 | 19 | it('Registrations are deleted properly', () => { 20 | cy.get('[data-cy=reg-row]').should('have.length', rows); 21 | cy.get('[data-cy=delete-button]').first().should('be.visible'); 22 | cy.get('[data-cy=delete-button]').first().click(); 23 | 24 | cy.get('[data-cy=confirm-delete-button]').click(); 25 | }); 26 | }); 27 | } 28 | } 29 | 30 | after(() => { 31 | for (const { slug } of happenings) { 32 | cy.visit(`/${registrationRoute}/${slug}`); 33 | cy.get('[data-cy=no-regs]').should('be.visible'); 34 | cy.get('[data-cy=no-regs]').should('contain.text', 'Ingen påmeldinger enda'); 35 | } 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "es2015"], 5 | "types": ["cypress"], 6 | "resolveJsonModule": true, 7 | "esModuleInterop": true 8 | }, 9 | "include": ["**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | frontend: 4 | build: . 5 | image: '${REGISTRY:-ghcr.io}/echo-webkom/echo-web-frontend:${GITHUB_SHA:-latest}' 6 | command: bash -c "yarn start & yarn cypress run --config video=false,screenshotOnRunFailure=false && kill $$!" 7 | # Don't start tests before backend is up. 8 | depends_on: 9 | backend: 10 | condition: service_healthy 11 | links: 12 | - backend 13 | ports: 14 | - '3000:3000' 15 | environment: 16 | BACKEND_URL: http://backend:8080 17 | # Values from .env file. 18 | SANITY_DATASET: ${SANITY_DATASET:?Must specify SANITY_DATASET in .env file or environment.} 19 | ADMIN_KEY: ${ADMIN_KEY:?Must specify ADMIN_KEY in .env file or environment.} 20 | NEXTAUTH_URL: http://localhost:3000 21 | NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-very-secret-string-123} 22 | 23 | backend: 24 | image: '${REGISTRY:-ghcr.io}/echo-webkom/echo-web-backend:${TAG:-latest-prod}' 25 | # Don't start backend before database is up. 26 | depends_on: 27 | database: 28 | condition: service_healthy 29 | links: 30 | - database 31 | ports: 32 | - '8080:8080' 33 | # Check if backend is ready, and insert bedpres for testing. 34 | healthcheck: 35 | test: ['CMD-SHELL', './scripts/submit_happening -t -x $$ADMIN_KEY || exit 1'] 36 | interval: 5s 37 | timeout: 5s 38 | retries: 5 39 | logging: 40 | driver: 'none' 41 | environment: 42 | DATABASE_URL: postgres://postgres:password@database/postgres 43 | # The value of DEV doesn't matter, only that it's defined. 44 | DEV: 'yes' 45 | # Values from .env file. 46 | ADMIN_KEY: ${ADMIN_KEY:?Must specify ADMIN_KEY in .env file or environment.} 47 | 48 | database: 49 | # Postgres 13.4 is the version Heroku uses. 50 | image: postgres:13.4-alpine 51 | restart: always 52 | ports: 53 | - '5432:5432' 54 | # Check if database is ready. 55 | healthcheck: 56 | test: ['CMD-SHELL', 'pg_isready -U postgres'] 57 | interval: 5s 58 | timeout: 5s 59 | retries: 5 60 | environment: 61 | POSTGRES_USER: postgres 62 | POSTGRES_PASSWORD: password 63 | POSTGRES_DB: postgres 64 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | 3 | Object.defineProperty(window, 'matchMedia', { 4 | writable: true, 5 | value: jest.fn().mockImplementation((query) => ({ 6 | matches: false, 7 | media: query, 8 | onchange: null, 9 | addListener: jest.fn(), // deprecated 10 | removeListener: jest.fn(), // deprecated 11 | addEventListener: jest.fn(), 12 | removeEventListener: jest.fn(), 13 | dispatchEvent: jest.fn(), 14 | })), 15 | }); 16 | 17 | process.env = { 18 | ...process.env, 19 | __NEXT_IMAGE_OPTS: { 20 | deviceSizes: [320, 420, 768, 1024, 1200], 21 | imageSizes: [], 22 | domains: ['images.ctfassets.net'], 23 | path: '/_next/image', 24 | loader: 'default', 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withPWA = require('next-pwa'); 2 | 3 | module.exports = withPWA({ 4 | pwa: { 5 | dest: 'public', 6 | disable: process.env.NODE_ENV === 'development', 7 | }, 8 | webpack: (config) => { 9 | config.module.rules.push({ 10 | test: /\.md$/, 11 | type: 'asset/source', 12 | }); 13 | return config; 14 | }, 15 | images: { 16 | domains: ['cdn.sanity.io'], 17 | }, 18 | experimental: { 19 | esmExternals: false, 20 | }, 21 | async redirects() { 22 | return [ 23 | { source: '/events', destination: '/event', permanent: true }, 24 | { 25 | source: '/events/:path', 26 | destination: '/event/:path', 27 | permanent: true, 28 | }, 29 | { source: '/bedpres/:path', destination: '/event/:path', permanent: true }, 30 | ]; 31 | }, 32 | reactStrictMode: true, 33 | }); 34 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/bedpres.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/bedpres.png -------------------------------------------------------------------------------- /public/bekk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/bekk.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #603cba 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/christmas-icons/santa_hat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/echo-logo-text-only-white-no-padding-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/echo-logo-text-only-white-no-padding-bottom.png -------------------------------------------------------------------------------- /public/echo-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/echo-logo-white.png -------------------------------------------------------------------------------- /public/echo-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/echo-logo.png -------------------------------------------------------------------------------- /public/event.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/event.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/favicon.ico -------------------------------------------------------------------------------- /public/halloween-icons/ghost.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 11 | 14 | 15 | 16 | 17 | 19 | 21 | 23 | 42 | 45 | 48 | 50 | 51 | -------------------------------------------------------------------------------- /public/halloween-icons/hat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 11 | 13 | 15 | 16 | 18 | 19 | 21 | 22 | 24 | 25 | 27 | 29 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /public/maskable-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/maskable-icon-192x192.png -------------------------------------------------------------------------------- /public/maskable-icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/maskable-icon-512x512.png -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echo-webkom/echo-web-frontend/bbaa8a0f302ada3946110ae0fc8d542e02f658a0/public/post.png -------------------------------------------------------------------------------- /public/powered-by-vercel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/sanity-logo.svg: -------------------------------------------------------------------------------- 1 | Sanity -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "echo – Linjeforeningen for informatikk", 3 | "description": "Nettsiden til echo – Linjeforeningen for informatikk.", 4 | "start_url": ".", 5 | "short_name": "echo", 6 | "shortcuts": [ 7 | { 8 | "name": "Bedriftspresentasjoner", 9 | "short_name": "Bedpres", 10 | "description": "Gå til bedriftspresentasjoner", 11 | "url": "/bedpres", 12 | "icons": [{ "src": "bedpres.png", "sizes": "120x120" }] 13 | }, 14 | { 15 | "name": "Arrangementer", 16 | "short_name": "Events", 17 | "description": "Gå til arrangementer", 18 | "url": "/event", 19 | "icons": [{ "src": "event.png", "sizes": "120x120" }] 20 | }, 21 | { 22 | "name": "Innlegg", 23 | "short_name": "Innlegg", 24 | "description": "Gå til innlegg", 25 | "url": "/posts", 26 | "icons": [{ "src": "post.png", "sizes": "120x120" }] 27 | } 28 | ], 29 | "icons": [ 30 | { 31 | "src": "/android-chrome-192x192.png", 32 | "sizes": "192x192", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "/android-chrome-512x512.png", 37 | "sizes": "512x512", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "/maskable-icon-512x512.png", 42 | "sizes": "512x512", 43 | "type": "image/png", 44 | "purpose": "maskable any" 45 | }, 46 | { 47 | "src": "/maskable-icon-192x192.png", 48 | "sizes": "192x192", 49 | "type": "image/png", 50 | "purpose": "maskable any" 51 | } 52 | ], 53 | "theme_color": "#E6E6E6", 54 | "background_color": "#FFFFFF", 55 | "display": "standalone" 56 | } 57 | -------------------------------------------------------------------------------- /public/static/valg.md: -------------------------------------------------------------------------------- 1 | **Husk å stemme innen fredag kl 23:59!** 2 | 3 | BLI MED Å BESTEMME HVEM SOM SKAL REPRESENTERE DEG OG DINE MENINGER I HOVEDSTYRE DET NESTE ÅRET! 4 | 5 | DIN VALGSEDDEL ER SENDT TIL DEG PÅ STUDENTMAILEN DIN. 6 | 7 | Det er rekordmange kandidater som stiller i år 🎉 Informasjon om alle kandidatene finner du her: [bit.ly/Appeller](https://bit.ly/Appeller) 8 | Valget stenger 6. mai klokken 2359. 9 | 10 | **Bruk stemmeretten din, og godt valg!** 11 | -------------------------------------------------------------------------------- /src/components/__tests__/footer.test.tsx: -------------------------------------------------------------------------------- 1 | import { screen } from '@testing-library/react'; 2 | import React from 'react'; 3 | import Footer from '../footer'; 4 | import { render } from './testing-utils'; 5 | 6 | describe('Footer', () => { 7 | test('renders without crashing', () => { 8 | render(