77 Commits
Author SHA1 Message Date
leander 0fc5a7a7b6 move footer orginfo to center of grid container 2025-08-06 01:19:55 +02:00
leander 864aef1b7b make dropdown menu buttons at least the length of the parent button 2025-08-06 01:17:43 +02:00
leander 50bdf6503e reintroduce translation support 2025-08-06 01:16:40 +02:00
leander b4bb073d87 minor event image width increase 2025-08-01 04:44:51 +02:00
leander 8e7f4b7659 make a mess and add max width to footer 2025-08-01 04:32:38 +02:00
leander b00debdbb0 make dropdown buttons thinner 2025-08-01 04:07:23 +02:00
leander bd6012b0ab fix(?) event image size 2025-08-01 03:59:55 +02:00
leander d313f903ee fix image alignment for about section 2025-08-01 03:45:47 +02:00
leander d9155dec10 fix fallback board member image handler 2025-08-01 03:30:00 +02:00
leander 95f75d9bb1 fix: photo credit text placement 2025-08-01 00:30:54 +02:00
leander d9a6019c7c reintroduce umami analytics support 2025-08-01 00:19:35 +02:00
leander ae59bfeb5f create deploy action 2025-07-31 23:36:45 +02:00
leander 171a3053f3 add image of website to readme 2025-07-31 22:56:51 +02:00
leander ee32c1723b Add docker support
Closes #31
2025-07-31 22:47:49 +02:00
leander e2d2836e69 Merge pull request #49 from UiO-Gaming/nextjs-rewrite
the big mega huge amazing nextjs migration
2025-07-31 23:41:16 +03:00
leander e7d7e29edf the big mega huge amazing nextjs migration
Code still trash though but we'll fix it someday

Closes #48, #47, #37, #32
2025-07-31 22:38:27 +02:00
leander e07e6ebcd0 remove deploy workflow 2025-07-22 02:06:28 +02:00
leander 0a3894e14d temporarily remove english translation
the site would not build due to some weird issue I have yet to figure
out. temporarily removing all english translations fixes it however. I
am probably migrating the site away from gatsby anyway so a temp fix is
fine
2025-07-22 02:05:19 +02:00
leander 626d749611 move action into the correct folder 2025-07-10 14:36:22 +02:00
leander cf84a5086e add deploy action 2025-07-10 14:34:33 +02:00
leander f2d7b15c4e stretch board member to same height 2025-07-02 21:35:06 +02:00
leander 5bd7321558 upgrade dependencies 2025-02-17 12:51:03 +01:00
Leander Furumo 400af2fb31 upgrade webhook-api dependencies 2025-02-12 12:47:12 +01:00
Leander Furumo c7185a79f3 upgrade dependencies 2025-02-12 12:45:30 +01:00
leander 2c6e4c2412 add tiktok link to contact section 2025-02-07 16:18:36 +01:00
leander 31a4b1cf81 reorder social links by importance 2025-02-07 16:16:29 +01:00
leander ef764a4b8d finish reverting npm migration 2025-02-07 16:06:54 +01:00
leander d21053b544 Revert "migrate webhook-api from yarn to npm"
This reverts commit dca3c08ee1.
2025-02-07 16:05:49 +01:00
leander 828468e70e Revert "make documentation reflect yarn to npm migration"
This reverts commit 7d17abc2c5.
2025-02-07 16:00:53 +01:00
leander 3d798a8913 Revert "migrate from yarn to npm"
This reverts commit 42c75194ed.
2025-02-07 16:00:23 +01:00
leander 7d17abc2c5 make documentation reflect yarn to npm migration 2025-01-16 12:13:51 +01:00
leander dff4669321 add rate limiting to webhook-api 2025-01-16 12:13:04 +01:00
leander dca3c08ee1 migrate webhook-api from yarn to npm 2025-01-16 12:11:36 +01:00
leander 42c75194ed migrate from yarn to npm 2025-01-16 12:00:04 +01:00
leander ead41eeaea upgrade dependencies 2024-11-07 19:22:28 +01:00
leander d20022899a reintroduce umami script 2024-11-07 19:15:04 +01:00
leander e0735a5c97 update 3D picture of office 2024-11-04 00:07:51 +01:00
leander cb7cdb8128 link using new repo name 2024-10-25 19:09:46 +02:00
leander 36ea11441f modify author 2024-10-25 19:08:46 +02:00
leander fda4846200 add codeowners file 2024-10-25 19:08:39 +02:00
leander 91dc9542e4 I give up
fuck javascript fuck javascript fuck javascript. nodejs was a mistake
2024-10-23 19:00:50 +02:00
leander 0e65652e76 attempt running nvm
fuck ubuntu and its outdated ass node version
2024-10-23 18:51:13 +02:00
leander 8a869a10eb install dependencies before running build on deploy
this is in case we introduce new deps
2024-10-23 17:26:58 +02:00
leander c0200f67f2 add auto-deploy workflow
hopefully it works. probably not knowing my luck
2024-10-23 16:48:23 +02:00
leander 26e2e75765 remove header text
looks ugly
2024-10-23 09:11:23 +02:00
leander d5131824d9 replace logo with 2024 rendition
might have to adjust the sizing later
2024-10-23 09:10:50 +02:00
centauridesigns 9394f1f7b4 minor css change for "other" button
hover behavior changed
2024-05-02 19:04:35 +02:00
LBlend 0f5c7306d3 use custom subdomain redirect for complaints 2024-05-02 13:40:55 +02:00
centauridesigns 9f7dacfb21 update "other" button for statutes 2024-05-01 23:21:08 +02:00
centauridesigns ca81402a45 add "other" button dropdown
new "other" dropdown which can be used for extra member-specific information that may be too important to restrict to footer. hovers are still overwritten, so the gray other button will hover purple. can be fixed later.
2024-05-01 22:33:21 +02:00
LBlend 39dfeb21a3 add senetence detailing our office gear 2024-04-19 02:49:47 +02:00
LBlend 3184215135 tell users about 3D pic functionality 2024-04-19 02:41:18 +02:00
LBlend 75c59eb8bb add new office 3D pic april 2024 2024-04-19 02:32:04 +02:00
LBlend 1e2444150b correct norwegian email translation 2024-03-18 14:42:35 +01:00
centauridesigns 51a58f5c55 remove logging and swap to i18n langauge handler 2024-03-15 16:41:29 +01:00
centauridesigns 9ff744e5b1 add translation support for cms board members
roles and bios are now also available in english.
2024-03-15 16:37:43 +01:00
LBlend d28db43afd translate eventpost metadescription 2024-03-14 16:11:58 +01:00
LBlend d7ad18c60f refactor datetime handling & format based on lang
Date format should now be rendered according to the language. Removed 'moment' dependency as we can use built in js to handle timezones as well as formatting. This is one of the tasks in issue #45
2024-03-14 14:45:23 +01:00
LBlend 6dacc4e5b5 translate metadata 2024-03-14 13:01:29 +01:00
LBlend 568b0524e0 add seo translation files 2024-03-14 13:00:14 +01:00
LBlend 1c7ca69a81 add addressRegion field to google rich snippet 2024-03-14 12:09:00 +01:00
LBlend 89d0e6ab9c add google rich snippet 2024-03-14 12:04:55 +01:00
LBlend c29e9d8f57 change statutes to english version for the english page 2024-03-14 01:20:23 +01:00
LBlend aec4a9406e initial translation support
I should've divided this into multiple commits but oh well. This is just initial as there are still parts that needs to be done. Particularly user generated content needs to be translated somehow. See issue #45 for more info
2024-03-13 02:36:31 +01:00
LBlend 224847b1a7 remove blog overview and room booking page 2024-03-13 02:32:52 +01:00
LBlend dc5ac4b2c7 migrate graphql queries to v3 2024-03-04 23:26:27 +01:00
LBlend 232f69472c change email field to contact info
Giving up your email shouldn't be necessary. Therefore, I have decided to change it to a general contact info field. This change is to simply reflect variable naming of the backend
2024-03-04 16:23:06 +01:00
LBlend 0a62a61ccf make board member email optional 2024-02-07 03:47:56 +01:00
LBlend 2857cc7048 upgrade dependencies 2024-02-07 03:39:26 +01:00
LBlend 37c9618ad6 fix unknown variable name issue 2024-02-07 03:35:53 +01:00
LBlend a9c250063c semantic variable naming 2024-02-07 03:11:08 +01:00
LBlend e2367eb29e change alt text 2024-02-07 03:08:18 +01:00
LBlend 8097ea89cd remove umami 2024-02-07 02:53:45 +01:00
LBlend 3d5c9d4bd9 add photo credit 2024-02-07 02:47:46 +01:00
LBlend fd5d8d1cf1 replace about and event section picture 2024-02-07 02:47:34 +01:00
LBlend 13c0eab15c close #43 2024-02-07 01:10:25 +01:00
LBlend 88df17818c change link selection color 2024-02-07 00:22:28 +01:00
100 changed files with 8578 additions and 32058 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
* @LBlend @centauridesigns
+28
View File
@@ -0,0 +1,28 @@
name: Deploy
on:
push:
branches:
- main
jobs:
run_pull:
runs-on: ubuntu-latest
steps:
- name: Set up SSH
run: |
install -m 600 -D /dev/null ~/.ssh/id_ed25519
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519 # This should be redundant but seems to do the trick.
ssh-keyscan -H ${{ vars.SSH_HOST }} > ~/.ssh/known_hosts
- name: Connect, pull, build and deploy
run: ssh ${{ secrets.SSH_USER }}@${{ vars.SSH_HOST }} "cd ${{ secrets.WORK_DIR }} && git checkout ${{ vars.MAIN_BRANCH }} && git pull && docker compose down && docker compose up -d --build && exit"
- name: Clean up
run: rm -rf ~/.ssh
permissions:
contents: read
pull-requests: write
+34 -62
View File
@@ -1,69 +1,41 @@
# Logs
logs
*.log
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# dotenv environment variable files
# env files (can opt-in for committing if needed)
.env*
# gatsby files
.cache/
public
# vercel
.vercel
# Mac files
.DS_Store
# Yarn
yarn-error.log
.pnp/
.pnp.js
# Yarn Integrity file
.yarn-integrity
# typescript
*.tsbuildinfo
next-env.d.ts
+2
View File
@@ -2,6 +2,8 @@
- Sørg for å formatere koden med Prettier
- Sjekk om nettsiden bygges uten problemer (`npm run build`)
- Commit messages samt issues skal helst være skrevet på engelsk
- Kode skal skrives på engelsk. Det vil si variabelnavn osv. Tekst som vises til bruker skal være på norsk
+15
View File
@@ -0,0 +1,15 @@
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
COPY package-lock.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 9000
CMD ["npm", "run", "start"]
+30 -17
View File
@@ -1,38 +1,51 @@
# uiogaming.no
Dette er repoet for UiO Gaming sin nettside. Nettsiden består av tre deler hvorav to av disse er i dette repoet. Her finner du frontend for nettsiden samt et webhook API. Dette APIet brukes til å trigge bygging av nettsiden når innhold endrer seg i CMS, backenden. Repoet for dette CMSet er [her](https://github.com/UiO-Gaming/uiogaming.no-backend). Nettsiden er bygget med GatsbyJS.
<img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/574daf4d-085c-4df0-ab86-7f843a1a403c" />
<br>
<br>
Dette er repoet for UiO Gaming sin nettside. Nettsiden består av to deler. Her finner du frontend for nettsiden. Repoet for CMSet er [her](https://github.com/UiO-Gaming/uiogaming.no-backend).
## Oppsett
### Frontend
1. Lag konfigurasjonsfil
1. Installer avhengigheter
I repoet er det en fil som heter [env.example](env.example). Kopier denne og sett navnet på den nye filen til `.env`.
Fyll så ut verdiene.
2. Installer avhengigheter
```bash
yarn install
npm install
```
2. Kjør nettsiden
3. Bygg nettsiden
```bash
yarn start
npm run build
```
4. Kjør nettsiden
```bash
npm start
```
Om du skal utvikle kan du kjøre følgende istedenfor steg 3 og 4
```bash
npm run dev
```
### Webhook API
Om du vil at innhold skal oppdatere seg når innholdet endres i backend må du kjøre dette APIet. Dette gjelder bare om du bruker en produksjonsversjon av nettsiden (`yarn serve`)
Om du vil at innhold skal oppdatere seg når innholdet endres i backend må du kjøre dette APIet. Dette gjelder bare om du kjører en produksjonsversjon av nettsiden
```bash
cd webhook-api
yarn install
yarn start
```
Du må spesifisere URL for webhook i Sanity sitt dashbord. URLen dette webhooket skal peke på er `https://uiogaming.no/api/revalidate` med _POST_ forespørselstype.
Du må også spesifisere URL for webhook i Sanity sitt dashbord. Du kan lese mer om det [her](https://www.sanity.io/docs/webhooks). Denne URLen skal peke på APIet ditt.
### Docker
Docker er for øyeblikket ikke støttet, men vil støttes fremtiden.
Du kan lese mer om det [her](https://www.sanity.io/docs/webhooks).
## Vil du bidra?
+6
View File
@@ -0,0 +1,6 @@
services:
uiogaming.no:
container_name: uiogaming.no
build: .
ports:
- 9000:9000
+7
View File
@@ -0,0 +1,7 @@
NEXT_PUBLIC_SANITY_PROJECT_ID=mmqlu667
NEXT_PUBLIC_SANITY_DATASET=production
SANITY_REVALIDATE_SECRET=your-super-secret-webhook-token-here
NEXT_PUBLIC_UMAMI_SRC=https://umami.uiogaming.no/script.js
NEXT_PUBLIC_UMAMI_WEBSITE_ID=1e985053-9158-4d62-be07-0fe83a9dce6e
+16
View File
@@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
export default eslintConfig;
-50
View File
@@ -1,50 +0,0 @@
module.exports = {
siteMetadata: {
title: `UiO Gaming`,
description: `En hobbyforening som ønsker skape et sosialt miljø rundt gaming og e-sport på UiO!`,
author: `@uiogaming`,
url: `https://uiogaming.no`,
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: "gatsby-plugin-manifest",
options: {
icon: "src/images/logo.jpg",
},
},
`gatsby-plugin-image`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-source-sanity`,
options: {
projectId: `mmqlu667`,
dataset: `production`,
watchMode: true,
},
},
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `UiO Gaming`,
short_name: `uiogaming.no`,
start_url: `/`,
background_color: `#1D1320`,
theme_color: `#7D36E7`,
display: `minimal-ui`,
icon: `src/images/logo.jpg`, // This path is relative to the root of the site.
},
},
// this (optional) plugin enables Progressive Web App + Offline functionality
// To learn more, visit: https://gatsby.dev/offline
// `gatsby-plugin-offline`,
],
}
-53
View File
@@ -1,53 +0,0 @@
const path = require("path")
module.exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const blogTemplate = path.resolve("./src/pages/blogPost.js")
const eventTemplate = path.resolve("./src/pages/eventPost.js")
const result = await graphql(`
query {
allSanityPost {
nodes {
_id
slug {
current
}
}
}
allSanityEvent {
nodes {
_id
slug {
current
}
}
}
}
`)
const posts = result.data.allSanityPost.nodes
posts.forEach(post => {
const path = `/blog/${post.slug.current}`
createPage({
path,
component: blogTemplate,
context: {
id: post._id,
},
})
})
const events = result.data.allSanityEvent.nodes
events.forEach(event => {
const path = `/event/${event.slug.current}`
createPage({
path,
component: eventTemplate,
context: {
id: event._id,
},
})
})
}
Vendored
+4
View File
@@ -0,0 +1,4 @@
declare module "*.module.css" {
const classes: { readonly [key: string]: string }
export = classes
}
+53
View File
@@ -0,0 +1,53 @@
{
"common": {
"photo": "Photo",
"join": "Become a member",
"statutes": "Statutes",
"complaint": "Complaint",
"documents": "Documents"
},
"buttons": {
"back": "back",
"flagImageAlt": "Norwegian flag"
},
"404": {
"title": "404: Page not found",
"text": "Our journey ended here. Please don't leave us :(",
"back": "Back to safe grounds"
},
"hero": {
"text": "A student organisation that aims to create an inclusive environment around gaming and e-sports for the students at UiO"
},
"about": {
"title": "Who are we?",
"imageAlt": "2 members playing together",
"text": "UiO Gaming is a student organisation with a primary focus on creating an inclusive environment around gaming and e-sports. We wish to establish a community where it’s easy to make new friends and cultivate your gaming interests. Each week we host both casual and more intense gamenights, as well as physical events such as board game nights and other social gatherings. Our main channel is a Discord server where you can meet new people with common interests and play games with other students.",
"interested": "Interested?",
"other": "Other"
},
"events": {
"title": "Upcoming Events",
"imageAlt": "Members playing board games",
"readMore": "Read more",
"noEvents": "No upcoming events"
},
"office": {
"title": "The Office",
"text": "We have an office at Chateau Neuf where you can use our gear and hang out with other members! We've got all the latest and greatest consoles, 6 high-end PCs as well as a plethora of board games.",
"tip": "Tip: You can look around the office by clicking and dragging the image below 👀"
},
"boardMembers": {
"title": "The Board",
"imageAlt": "Potrait of board member",
"noContact": "No contact info"
},
"contact": {
"title": "Find us",
"email": "E-mail"
},
"footer": {
"sourceCode": "Source code",
"admin": "Admin page",
"orgNum": "Org. No."
}
}
+53
View File
@@ -0,0 +1,53 @@
{
"common": {
"photo": "Foto",
"join": "Bli medlem",
"statutes": "Vedtekter",
"complaint": "Klage",
"documents": "Dokumenter"
},
"buttons": {
"back": "tilbake",
"flagImageAlt": "Britisk flagg"
},
"404": {
"title": "404: Siden finnes ikke",
"text": "Du har nådd en blindvei, men ikke på Blindern",
"back": "Tilbake til hjemmesiden"
},
"hero": {
"text": "En hobbyforening som ønsker å skape et sosialt miljø rundt gaming og e-sport på UiO"
},
"about": {
"title": "Hvem er vi?",
"imageAlt": "2 medlemmer som spiller sammen",
"text": "UiO Gaming er en forening med hovedfokus på å danne et inkluderende fellesskap rundt videospill og e-sport. Hos oss er det enkelt å skaffe seg venner, ettersom alle har felles interesser. Vi arrangerer ukentlige spillkvelder og fysiske sosiale arrangementer, som for eksempel brettspillkvelder og sosiale sammenkomster. Vår hovedkanal er en aktiv Discord-server hvor man kan møte nye folk med felles interesser og game med andre studenter.",
"interested": "Interessert?",
"other": "Annet"
},
"events": {
"title": "Hva skjer?",
"imageAlt": "Medlemmer som spiller brettspill",
"readMore": "Les mer",
"noEvents": "Ingen kommende arrangementer"
},
"office": {
"title": "Kontoret",
"text": "Vi har et foreningskontor på Chateau Neuf der man kan bruke utstyret vårt og henge med andre medlemmer! Vi har blant annet alle de nyeste konsollene, 6 gaming-PCer samt et bredt utvalg av brettspill.",
"tip": "Tips: Du kan kikke rundt i kontoret ved å klikke og dra på bildet nedenfor 👀"
},
"boardMembers": {
"title": "Styret",
"imageAlt": "Bilde av styremedlem",
"noContact": "Ingen kontaktinfo"
},
"contact": {
"title": "Finn oss",
"email": "E-post"
},
"footer": {
"sourceCode": "Kildekode",
"admin": "Adminområdet",
"orgNum": "Org.Nr."
}
}
+19
View File
@@ -0,0 +1,19 @@
import type { NextConfig } from "next"
import createNextIntlPlugin from "next-intl/plugin"
const withNextIntl = createNextIntlPlugin()
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "cdn.sanity.io",
port: "",
pathname: "/images/**",
},
],
},
}
export default withNextIntl(nextConfig)
+6614 -18621
View File
File diff suppressed because it is too large. Load diff
+29 -28
View File
@@ -1,38 +1,39 @@
{
"name": "uiogaming.no",
"version": "1.0.0",
"private": true,
"description": "UiO Gaming Website",
"version": "0.2.0",
"author": "LBlend <leanderfurumo@protonmail.com",
"author": "Leander Furumo <leander@furumo.eu>",
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start -p 9000",
"lint": "next lint",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\""
},
"dependencies": {
"@sanity/block-content-to-react": "^3.0.0",
"gatsby": "^3.4.1",
"gatsby-plugin-image": "^1.4.0",
"gatsby-plugin-manifest": "^3.4.0",
"gatsby-plugin-react-helmet": "^4.4.0",
"gatsby-plugin-sharp": "^3.7.1",
"gatsby-source-filesystem": "^3.7.1",
"gatsby-source-sanity": "^7.0.4",
"gatsby-transformer-sharp": "^3.7.1",
"moment-timezone": "^0.5.33",
"prop-types": "^15.7.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-helmet": "^6.1.0",
"react-icons": "^4.2.0",
"react-pannellum": "^0.2.14"
"@portabletext/react": "^3.2.1",
"@sanity/client": "^7.8.1",
"@sanity/image-url": "^1.1.0",
"country-flag-icons": "^1.5.19",
"install": "^0.13.0",
"next": "15.4.4",
"next-intl": "^4.3.4",
"npm": "^11.5.2",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-icons": "^5.5.0",
"react-photo-sphere-viewer": "^6.2.3"
},
"devDependencies": {
"prettier": "2.2.1"
},
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
"start": "npm run develop",
"serve": "gatsby serve",
"clean": "gatsby clean",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1"
"@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.4.4",
"prettier": "^3.6.2",
"typescript": "^5"
},
"repository": {
"type": "git",
File renamed without changes.

Before

Width:  |  Height:  |  Size: 5.7 MiB

After

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 679 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 696 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

+43
View File
@@ -0,0 +1,43 @@
import { revalidateTag } from "next/cache"
import { NextRequest, NextResponse } from "next/server"
export async function POST(request: NextRequest) {
const secret = request.nextUrl.searchParams.get("secret")
if (secret !== process.env.SANITY_REVALIDATE_SECRET) {
return NextResponse.json({ message: "Invalid secret" }, { status: 401 })
}
try {
const body = await request.json()
const { _type, slug } = body
console.log(
`Revalidating content: ${_type}${slug ? ` (${slug.current})` : ""}`
)
if (_type === "post") {
revalidateTag("blog-posts")
if (slug?.current) {
revalidateTag(`post-${slug.current}`)
}
} else if (_type === "event") {
revalidateTag("events")
if (slug?.current) {
revalidateTag(`event-${slug.current}`)
}
}
revalidateTag("home")
return NextResponse.json({
revalidated: true,
now: Date.now(),
type: _type,
slug: slug?.current,
})
} catch (err) {
console.error("Error revalidating:", err)
return NextResponse.json({ message: "Error revalidating" }, { status: 500 })
}
}
@@ -9,6 +9,7 @@
.article {
display: grid;
place-items: center;
margin-bottom: 2rem;
}
.header {
display: grid;
@@ -21,6 +22,9 @@
.headerImage {
padding: 0;
margin: 1rem 0;
width: 100%;
height: auto;
object-fit: contain;
max-width: 100%;
max-height: 80vh;
place-self: center;
@@ -53,6 +57,7 @@
display: grid;
gap: 2em;
}
.content {
display: grid;
gap: 1rem;
@@ -60,11 +65,6 @@
padding: 0 1rem;
}
/* Block content styling */
/* .content {
padding-inline-start: 1rem;
border-inline-start: 8px solid var(--main-color);
} */
.content blockquote {
color: rgb(225, 225, 225);
font-weight: 100;
@@ -77,18 +77,9 @@
.content h2,
.content h3,
.content h4 {
margin-top: 3rem;
margin-top: 2rem;
}
/* .content blockquote::before {
content: '"';
font-size: 3rem;
}
.content blockquote::after {
content: '"';
font-size: 3rem;
} */
@media (min-width: 60em) {
.content {
padding: 0;
+161
View File
@@ -0,0 +1,161 @@
import Layout from "@/app/layout"
import Footer from "@/components/page-sections/footer"
import { generateSeoMetadata, generateSeoViewport } from "@/components/seo"
import Back from "@/components/ui/back"
import { urlFor } from "@/lib/imageUrlBuilder"
import { getBlogPost } from "@/lib/sanity"
import { PortableText, PortableTextComponents } from "@portabletext/react"
import { Metadata, Viewport } from "next"
import { getLocale } from "next-intl/server"
import Image from "next/image"
import * as styles from "./page.module.css"
interface BlogPageParams {
params: Promise<{
slug: string
}>
}
interface ImageValue {
asset?: {
_id: string
url: string
}
alt?: string
caption?: string
}
interface LinkValue {
href: string
}
export async function generateMetadata({
params,
}: BlogPageParams): Promise<Metadata> {
const { slug } = await params
const data = await getBlogPost(slug)
if (!data) {
return {
title: "Blog Post Not Found | UiO Gaming",
}
}
return generateSeoMetadata({
title: data.title,
description: data.excerpt,
imageURL: data.mainImage?.asset?.url,
author: data.author?.name,
})
}
export function generateViewport(): Viewport {
return generateSeoViewport()
}
const components: PortableTextComponents = {
types: {
image: ({ value }: { value: ImageValue }) => {
if (!value?.asset) return null
return (
<div className={styles.imageContainer}>
<Image
src={urlFor(value).url()}
alt={value.alt || "Blog image"}
width={800}
height={400}
style={{ width: "100%", height: "auto" }}
/>
{value.caption && (
<p className={styles.imageCaption}>{value.caption}</p>
)}
</div>
)
},
},
marks: {
link: ({
children,
value,
}: {
children?: React.ReactNode
value?: LinkValue
}) => (
<a
href={value?.href ?? "#"}
target="_blank"
rel="noopener noreferrer"
className={styles.link}
>
{children}
</a>
),
},
}
const BlogPost = async ({ params }: BlogPageParams) => {
const { slug }: { slug: string } = await params
const locale = await getLocale()
const data = await getBlogPost(slug)
if (!data) {
throw new Error("Blog post not found")
}
const createdAt = new Date(data._createdAt).toLocaleDateString(locale, {
day: "numeric",
month: "long",
year: "numeric",
})
return (
<Layout>
<div className={styles.container}>
<div className={styles.backButton}>
<Back />
</div>
<article className={styles.article}>
<header className={styles.header}>
{data.mainImage && (
<Image
src={urlFor(data.mainImage).url()}
alt={data.title}
width={800}
height={400}
className={styles.headerImage}
/>
)}
<h1 className={styles.title}>{data.title}</h1>
<p className={styles.excerpt}>{data.excerpt}</p>
<div className={styles.author}>
{data.author?.image && (
<Image
className="circular"
alt={`Forfatteren av innlegget, ${data.author.name}`}
src={urlFor(data.author.image).width(50).height(50).url()}
width={50}
height={50}
/>
)}
<div className={styles.authorNameSection}>
<p>{data.author?.name}</p>
<p>{createdAt}</p>
</div>
</div>
</header>
<div className={styles.content}>
<PortableText value={data.body} components={components} />
</div>
</article>
</div>
<Footer />
</Layout>
)
}
export default BlogPost
@@ -1,12 +1,7 @@
body {
display: grid;
place-content: center;
min-height: none;
}
.container {
display: grid;
place-items: center;
place-content: center;
min-height: 100vh;
margin: 1rem;
}
@@ -37,4 +32,5 @@ body {
.description {
margin-top: 1rem;
white-space: pre-wrap;
}
+98
View File
@@ -0,0 +1,98 @@
import { generateSeoMetadata, generateSeoViewport } from "@/components/seo"
import Back from "@/components/ui/back"
import { getEvent } from "@/lib/sanity"
import { Metadata, Viewport } from "next"
import { getLocale } from "next-intl/server"
import { FaCalendarAlt, FaMapMarkerAlt } from "react-icons/fa"
import * as styles from "./page.module.css"
interface EventPageParams {
params: Promise<{
slug: string
}>
}
export async function generateMetadata({
params,
}: EventPageParams): Promise<Metadata> {
const { slug } = await params
const data = await getEvent(slug)
if (!data) {
return {
title: "Event Not Found | UiO Gaming",
}
}
const formatDate = (dateString: string) => {
const date = new Date(dateString)
const options: Intl.DateTimeFormatOptions = {
weekday: "long",
month: "long",
year: "numeric",
day: "numeric",
hour: "numeric",
minute: "2-digit",
timeZone: "Europe/Oslo",
}
return date.toLocaleString("no", options)
}
return generateSeoMetadata({
title: data.title,
description: `Tid: ${formatDate(data.date)}\nSted: ${data.location}\n\n${data.description}`,
})
}
export function generateViewport(): Viewport {
return generateSeoViewport()
}
const Event = async ({ params }: EventPageParams) => {
const { slug }: { slug: string } = await params
const locale = await getLocale()
const data = await getEvent(slug)
if (!data) {
throw new Error("Event not found")
}
const formatDate = (dateString: string) => {
const date = new Date(dateString)
const options: Intl.DateTimeFormatOptions = {
weekday: "long",
month: "long",
year: "numeric",
day: "numeric",
hour: "numeric",
minute: "2-digit",
timeZone: "Europe/Oslo",
}
return date.toLocaleString(locale, options)
}
return (
<div className={styles.container}>
<Back />
<div className={styles.event}>
<h1>{data.title}</h1>
<div className={styles.metadata}>
<FaMapMarkerAlt />
<p>{data.location}</p>
</div>
<div className={styles.metadata}>
<FaCalendarAlt />
<p>{formatDate(data.date)}</p>
</div>
<p className={styles.description}>{data.description}</p>
</div>
</div>
)
}
export default Event
@@ -3,6 +3,7 @@
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 1rem;
}
+43
View File
@@ -0,0 +1,43 @@
import Layout from "@/app/layout"
import { generateSeoMetadata, generateSeoViewport } from "@/components/seo"
import { Metadata, Viewport } from "next"
import { useTranslations } from "next-intl"
import Link from "next/link"
import * as styles from "./global-not-found.module.css"
export function generateMetadata(): Metadata {
return generateSeoMetadata({
title: "404: Ikke funnet",
description: "Uh oh :(",
})
}
export function generateViewport(): Viewport {
return generateSeoViewport()
}
const NotFoundPage = () => {
const t = useTranslations("404")
return (
<Layout>
<div className={styles.container}>
<h1 className={styles.centeredText}>{t("title")}</h1>
<p className={styles.centeredText}>
{t("text")}{" "}
<span role="img" aria-label="eyes">
👀
</span>
</p>
<Link href="/" className={`${styles.centeredText} ${styles.homeLink}`}>
{t("back")}
</Link>
</div>
</Layout>
)
}
export default NotFoundPage
+20 -13
View File
@@ -1,16 +1,18 @@
@import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,400;0,900;1,100;1,400;1,900&display=swap");
/* ===== Variables ===== */
:root {
--main-color: #7D36E7;
--main-background-color: #1D1320;
--secondary-background-color: #261F31;
--main-color: #7d36e7;
--main-background-color: #1d1320;
--secondary-background-color: #261f31;
--footer-color: rgb(12, 12, 15);
--header-color: rgb(255, 255, 255);
--text-color: #EEEEEE;
--light-text-color: #CCCCCC;
--link-color: #7D36E7;
--link-color-hover: #D1B3FF;
--text-color: #eeeeee;
--light-text-color: #cccccc;
--link-color: #7d36e7;
--link-color-hover: #d1b3ff;
--main-font: "Nunito", sans-serif;
@@ -90,8 +92,6 @@ select {
/* ===== Base styling ===== */
@import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,400;0,900;1,100;1,400;1,900&display=swap");
::-webkit-scrollbar {
width: 0.5em;
}
@@ -107,7 +107,7 @@ select {
background-color: var(--main-color);
}
a::selection {
color: blue;
color: white;
}
body {
@@ -161,6 +161,7 @@ section {
padding: 7em 1em;
display: grid;
justify-content: center;
border-radius: 1rem;
}
section:nth-child(2n + 1) {
background-color: var(--secondary-background-color);
@@ -173,9 +174,9 @@ section h2 {
text-align: center;
justify-self: center;
}
@media (min-width: 60em) {
@media (min-width: 70em) {
section {
padding: 10em 5em;
padding: 10em 4em;
}
}
@@ -219,7 +220,13 @@ section h2 {
mask-image: radial-gradient(white, black);
-webkit-mask-image: -webkit-radial-gradient(white, black);
}
@media (min-width: 60em) {
.photo-credit,
.photo-credit:hover {
color: var(--light-text-color);
text-align: end;
place-self: end;
}
@media (min-width: 70em) {
.no-mobile {
display: block;
}
+48
View File
@@ -0,0 +1,48 @@
import "@/app/globals.css"
import { LocaleProvider } from "@/components/localeProvider"
import UmamiAnalytics from "@/components/umamiAnalytics"
import { getAnalyticsConfig } from "@/lib/analyticsConfig"
import type { Metadata } from "next"
import { getLocale } from "next-intl/server"
export const metadata: Metadata = {
title: "UiO Gaming",
description:
"En hobbyforening som ønsker skape et sosialt miljø rundt gaming og e-sport på UiO!",
icons: {
icon: "/logo_2024_neon_pink.png",
shortcut: "/logo_2024_neon_pink.png",
apple: "/logo_2024_neon_pink.png",
},
}
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
const locale = (await getLocale()) as "no" | "en"
const messages = (await import(`../../messages/${locale}.json`)).default // TODO: duplicate of request.ts?
const analyticsConfig = getAnalyticsConfig()
return (
<html lang={locale}>
<body>
<LocaleProvider locale={locale} messages={messages}>
<main>{children}</main>
{analyticsConfig?.enabled && (
<UmamiAnalytics
websiteId={analyticsConfig.websiteId}
src={analyticsConfig.src}
domains={analyticsConfig.domains}
/>
)}
</LocaleProvider>
</body>
</html>
)
}
+36
View File
@@ -0,0 +1,36 @@
import Layout from "@/app/layout"
import About from "@/components/page-sections/about"
import BoardMembers from "@/components/page-sections/boardMembers"
import Contact from "@/components/page-sections/contact"
import Events from "@/components/page-sections/events"
import Footer from "@/components/page-sections/footer"
import Hero from "@/components/page-sections/hero"
import Office from "@/components/page-sections/office"
import { generateSeoMetadata, generateSeoViewport } from "@/components/seo"
import { Metadata, Viewport } from "next"
export const revalidate = 3600 // 1 hour
export function generateMetadata(): Metadata {
return generateSeoMetadata({})
}
export function generateViewport(): Viewport {
return generateSeoViewport()
}
export default function Home() {
return (
<Layout>
<Hero />
<About />
<Events />
<Office />
<BoardMembers />
<Contact />
<Footer />
</Layout>
)
}
-16
View File
@@ -1,16 +0,0 @@
import * as React from "react"
import * as styles from "./back.module.css"
import { FaArrowLeft } from "react-icons/fa"
const Back = () => (
<button
className={`${styles.container} ${styles.button}`}
onClick={() => window.history.back()}
>
<FaArrowLeft />
<p>Tilbake</p>
</button>
)
export default Back
-14
View File
@@ -1,14 +0,0 @@
import * as React from "react"
import * as styles from "./memberButton.module.css"
const MemberButton = () => (
<a
href="https://innmelding.uiogaming.no"
target="_blank"
rel="noreferrer"
className={styles.button}
>
Bli medlem!
</a>
)
export default MemberButton
@@ -1,16 +0,0 @@
.button {
display: flex;
justify-content: center;
font-size: 1.6rem;
background-color: var(--main-color);
padding: 0.5em 1em;
border-radius: 999em;
color: var(--text-color);
}
.button:hover {
color: white;
background-color: #A76CFF;
}
-16
View File
@@ -1,16 +0,0 @@
import * as React from "react"
import PropTypes from "prop-types"
const Layout = ({ children }) => {
return (
<>
<main>{children}</main>
</>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
+24
View File
@@ -0,0 +1,24 @@
.button {
display: flex;
gap: 0.25rem;
position: fixed;
top: 0.5rem;
right: 0.5rem;
z-index: 0;
background: pink;
color: black;
border: 1px solid black;
font-size: 1.1rem;
border-radius: 0.5rem;
padding-inline: 0.5em;
}
.button:hover {
background: #ff7b7b;
color: white;
}
.flag {
width: 1.2rem;
}
+63
View File
@@ -0,0 +1,63 @@
"use client"
import { GB, NO } from "country-flag-icons/react/3x2"
import { NextIntlClientProvider } from "next-intl"
import { ReactNode, useEffect, useState } from "react"
import * as styles from "./localeProvider.module.css"
type LocaleProviderProps = {
locale: "no" | "en"
messages: Record<string, string>
children: ReactNode
}
export function LocaleProvider({
locale: serverLocale,
messages,
children,
}: LocaleProviderProps) {
const [locale, setLocale] = useState<"no" | "en">(serverLocale)
const [loadedMessages, setLoadedMessages] =
useState<Record<string, string>>(messages)
useEffect(() => {
const match = document.cookie.match(/(?:^|; )locale=([^;]+)/)
const saved = match ? decodeURIComponent(match[1]) : null
if (saved && saved !== locale) {
setLocale(saved as "no" | "en")
import(`../../messages/${saved}.json`).then(module => {
setLoadedMessages(module.default)
})
}
}, [locale])
const changeLocale = (newLocale: "no" | "en") => {
setLocale(newLocale)
document.cookie = `locale=${encodeURIComponent(
newLocale
)}; path=/; max-age=31536000; SameSite=Lax`
window.location.reload()
}
return (
<NextIntlClientProvider locale={locale} messages={loadedMessages}>
{children}
<div className={styles.languageSwitcher}>
{locale === "no" ? (
<button onClick={() => changeLocale("en")} className={styles.button}>
<GB className={styles.flag} title="English flag" />
English
</button>
) : (
<button onClick={() => changeLocale("no")} className={styles.button}>
<NO className={styles.flag} title="Norwegian flag" />
Norsk
</button>
)}
</div>
</NextIntlClientProvider>
)
}
@@ -4,16 +4,11 @@
gap: 10rem;
}
.textContent {
display: grid;
place-items: center;
}
.joinSection {
display: grid;
grid-template-rows: auto auto;
place-items: center;
margin: 3.5rem 0;
margin-top: 3.5rem;
font-size: 2rem;
}
.joinSection h3 {
@@ -24,8 +19,19 @@
flex-direction: column;
gap: 1rem;
}
.image {
height: 100%;
flex-direction: column;
align-items: stretch;
justify-content: center;
}
.image img {
border-radius: 1rem;
object-fit: cover;
height: 80%;
}
@media (min-width: 60em) {
@media (min-width: 70em) {
.container {
grid-template-columns: 1fr 1fr;
gap: 3rem;
@@ -36,4 +42,10 @@
.buttons {
flex-direction: row;
}
.image {
display: flex;
align-self: center;
justify-self: right;
height: 100%;
}
}
+47
View File
@@ -0,0 +1,47 @@
import DiscordButton from "@components/ui/discordButton"
import MemberButton from "@components/ui/memberButton"
import OtherButton from "@components/ui/otherButton"
import { useTranslations } from "next-intl"
import Image from "next/image"
import * as styles from "./about.module.css"
const About = () => {
const t = useTranslations("about")
const tCommon = useTranslations("common")
return (
<section>
<div className={styles.container}>
<div className={"no-mobile " + styles.image}>
<Image
src="/about.jpg"
alt={t("imageAlt")}
width={640}
height={480}
/>
<p className="photo-credit">
{tCommon("photo")}: 장태민, Studentenes Fotoklubb
</p>
</div>
<div>
<h2 className="left-section-header">{t("title")}</h2>
<div>
<p>{t("text")}</p>
<div className={styles.joinSection}>
<h3>{t("interested")}</h3>
<div className={styles.buttons}>
<MemberButton />
<DiscordButton />
<OtherButton />
</div>
</div>
</div>
</div>
</div>
</section>
)
}
export default About
@@ -1,9 +1,9 @@
.container {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
align-items: stretch;
justify-content: space-evenly;
gap: 7rem;
gap: 4rem;
word-break: break-word;
}
@@ -39,11 +39,12 @@
}
.headerImageContainer {
border: 5px solid white;
display: inline-block;
}
/* .headerImage {
box-shadow: inset 0 0 10px #000000;
} */
.headerImageContainer img {
object-fit: cover;
height: auto;
width: 100%;
}
.info {
border-bottom: var(--card-section-divider);
@@ -65,6 +66,7 @@
border-top: var(--card-section-divider);
width: 100%;
font-size: 1.1rem;
margin-top: auto;
}
.footer > p {
margin-top: 2rem;
@@ -72,7 +74,7 @@
word-wrap: break-word;
}
@media (min-width: 60em) {
@media (min-width: 70em) {
.header {
flex-direction: row;
}
@@ -0,0 +1,105 @@
import { urlFor } from "@/lib/imageUrlBuilder"
import { sanityClient } from "@/lib/sanity"
import { getLocale, getTranslations } from "next-intl/server"
import Image from "next/image"
import * as styles from "./boardMembers.module.css"
interface BoardMember {
_id: string
boardMember: boolean
contactInfo?: string
name: string
role?: {
no: string
en: string
}
image?: {
asset?: {
_id: string
url: string
}
}
bio?: {
no: string
en: string
}
}
const BoardMembers = async () => {
const t = await getTranslations("boardMembers")
const locale = (await getLocale()) as "no" | "en"
const query = `
*[_type == "author" && boardMember == true] | order(orderRank asc) {
_id,
boardMember,
contactInfo,
name,
role {
no,
en
},
image {
asset->{
_id,
url
}
},
bio {
no,
en
}
}
`
const data: BoardMember[] = await sanityClient.fetch(query)
return (
<section>
<h2>{t("title")}</h2>
<div className={styles.container}>
{data.map((member: BoardMember) => (
<article key={member._id} className={styles.card}>
<div className={styles.header}>
<div
className={`circular-container ${styles.headerImageContainer}`}
>
<Image
src={
member.image?.asset?.url
? urlFor(member.image.asset.url)
.width(512)
.height(512)
.url()
: "/person.png"
}
width={256}
height={256}
className="circular"
alt={`${t("imageAlt")}, ${member.name}`}
/>
</div>
<div className={styles.info}>
<h3>{member.name}</h3>
<p>{member.role?.[locale]}</p>
</div>
</div>
<p>{member.bio?.[locale]}</p>
<div className={styles.footer}>
<p className="text-center">
{member.contactInfo ? (
member.contactInfo
) : (
<i>{t("noContact")}</i>
)}
</p>
</div>
</article>
))}
</div>
</section>
)
}
export default BoardMembers
+104
View File
@@ -0,0 +1,104 @@
import { useTranslations } from "next-intl"
import * as styles from "./contact.module.css"
import {
FaAt,
FaDiscord,
FaFacebook,
FaGithub,
FaInstagram,
FaTiktok,
FaTwitch,
FaYoutube,
} from "react-icons/fa"
const Contact = () => {
const t = useTranslations("contact")
return (
<section>
<h2 className="section-center-header">{t("title")}</h2>
<div className={styles.container}>
<div className={styles.item}>
<FaAt />
<a href="mailto:styret@uiogaming.no" target="_blank" rel="noreferrer">
{t("email")}
</a>
</div>
<div className={styles.item}>
<FaDiscord />
<a
href="https://discord.uiogaming.no"
target="_blank"
rel="noreferrer"
>
Discord
</a>
</div>
<div className={styles.item}>
<FaInstagram />
<a
href="https://instagram.uiogaming.no"
target="_blank"
rel="noreferrer"
>
Instagram
</a>
</div>
<div className={styles.item}>
<FaTiktok />
<a
href="https://tiktok.uiogaming.no"
target="_blank"
rel="noreferrer"
>
TikTok
</a>
</div>
<div className={styles.item}>
<FaTwitch />
<a
href="https://twitch.uiogaming.no"
target="_blank"
rel="noreferrer"
>
Twitch
</a>
</div>
<div className={styles.item}>
<FaYoutube />
<a
href="https://youtube.uiogaming.no"
target="_blank"
rel="noreferrer"
>
YouTube
</a>
</div>
<div className={styles.item}>
<FaFacebook />
<a
href="https://facebook.uiogaming.no"
target="_blank"
rel="noreferrer"
>
Facebook
</a>
</div>
<div className={styles.item}>
<FaGithub />
<a
href="https://github.uiogaming.no"
target="_blank"
rel="noreferrer"
>
GitHub
</a>
</div>
</div>
</section>
)
}
export default Contact
@@ -5,7 +5,7 @@
.container {
display: grid;
place-items: center;
gap: 6em;
gap: 4em;
grid-template-rows: auto 1fr;
}
.container > h2 {
@@ -23,7 +23,7 @@
gap: 2rem;
}
.events > *,
*:hover {
.events > *:hover {
color: white;
}
.events > h2 {
@@ -59,7 +59,7 @@
grid-template-columns: auto 1fr;
align-items: center;
font-weight: 100;
color: var(--light-text-color)
color: var(--light-text-color);
}
.metadata > *:hover {
color: var(--light-text-color);
@@ -67,8 +67,11 @@
.image {
grid-row: 1 / -1;
align-self: center;
justify-self: left;
margin-top: 2em;
}
@media (min-width: 60em) {
@media (min-width: 70em) {
.stretch {
justify-content: center;
}
@@ -76,15 +79,16 @@
grid-template-columns: 1fr 1fr;
gap: 3rem;
}
.container > h2 {
margin-bottom: 1em;
}
.sectionHeader {
margin-bottom: 0.5em;
}
.card {
padding: 2em;
}
.image {
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: center;
height: 90%;
}
}
.readMore {
+113
View File
@@ -0,0 +1,113 @@
import { getAllEvents } from "@/lib/sanity"
import { getLocale, getTranslations } from "next-intl/server"
import Image from "next/image"
import Link from "next/link"
import { FaCalendarAlt, FaMapMarkerAlt } from "react-icons/fa"
import * as styles from "./events.module.css"
interface Event {
_id: string
title: string
location: string
date: string
slug: {
current: string
}
description: string
}
const Events = async () => {
const t = await getTranslations("events")
const tCommon = await getTranslations("common")
const locale = await getLocale()
const data: Event[] = await getAllEvents()
const currentDate = new Date()
const filteredEvents = data
.filter((event: Event) => {
return currentDate <= new Date(event.date)
})
.slice(0, 2)
const formatDate = (dateString: string) => {
const date = new Date(dateString)
const options: Intl.DateTimeFormatOptions = {
weekday: "long",
month: "long",
year: "numeric",
day: "numeric",
hour: "numeric",
minute: "2-digit",
timeZone: "Europe/Oslo",
}
return date.toLocaleString(locale, options)
}
const mapEvents = (events: Event[]) => {
if (events.length !== 0) {
return events.map((event: Event) => (
<Link key={event._id} href={"/event/" + event.slug.current}>
<article className={styles.card}>
<h3>{event.title}</h3>
<div>
<div className={styles.metadata}>
<FaMapMarkerAlt />
<p>{event.location}</p>
</div>
<div className={styles.metadata}>
<FaCalendarAlt />
<p>{formatDate(event.date)}</p>
</div>
</div>
<p>
{event.description.length < 70 ? (
event.description
) : (
<>
{event.description.substring(0, 70)}...{" "}
<span className={styles.readMore}>{t("readMore")}</span>
</>
)}
</p>
</article>
</Link>
))
} else {
return (
<h3 className={styles.noEvents}>
{t("noEvents")}
<br />
<br />
(っ˘̩╭╮˘̩)っ
</h3>
)
}
}
return (
<section>
<div className={styles.container}>
<h2 className={styles.sectionHeader}>{t("title")}</h2>
<div className={styles.events}>{mapEvents(filteredEvents)}</div>
<div className={"no-mobile " + styles.image}>
<Image
src="/event.jpg"
width={850}
height={480}
style={{ borderRadius: "1rem", objectFit: "cover", height: "100%" }}
alt={t("imageAlt")}
/>
<p className="photo-credit">
{tCommon("photo")}: 장태민, Studentenes Fotoklubb
</p>
</div>
</div>
</section>
)
}
export default Events
@@ -1,26 +1,37 @@
footer {
.container {
background-color: var(--footer-color);
padding: 10rem 3rem;
display: grid;
place-items: center;
justify-items: space-around;
align-items: center;
min-width: 100%;
}
.footer {
max-width: 1280px;
width: 100%;
justify-self: center;
padding: 10rem 3rem;
gap: 4rem;
display: grid;
place-items: center;
gap: 2rem;
grid-template-rows: auto;
align-items: center;
}
.orgInfo {
text-align: center;
justify-self: center;
}
.orgInfo * {
margin: 1rem 0;
}
.links {
justify-self: center;
}
.links * {
margin: 0.75rem 0;
}
.logo {
justify-self: center;
}
@media (min-width: 70em) {
.footer {
@@ -28,6 +39,10 @@ footer {
}
.orgInfo {
text-align: left;
justify-self: center;
}
.logo {
justify-self: right;
}
.links {
justify-self: start;
+68
View File
@@ -0,0 +1,68 @@
import { useLocale, useTranslations } from "next-intl"
import Image from "next/image"
import * as styles from "./footer.module.css"
const Footer = () => {
const t = useTranslations("footer")
const tCommon = useTranslations("common")
const locale = useLocale()
const statutesUrl =
locale === "en"
? "https://statutes.uiogaming.no"
: "https://vedtekter.uiogaming.no"
return (
<footer className={styles.container}>
<div className={styles.footer}>
<Image
src="/logo_2024_neon_trans.png"
alt="UiO Gaming logo"
className={styles.logo}
width={187}
height={256}
/>
<div className={styles.links}>
<ul className="remove-bullets">
<li>
<a href={statutesUrl} rel="noreferrer">
{tCommon("statutes")}
</a>
</li>
<li>
<a href="https://innmelding.uiogaming.no" rel="noreferrer">
{tCommon("join")}
</a>
</li>
<li>
<a href="https://klage.uiogaming.no" rel="noreferrer">
{tCommon("complaint")}
</a>
</li>
<li>
<a
href="https://github.com/UiO-Gaming/uiogaming.no/"
rel="noreferrer"
>
{t("sourceCode")}
</a>
</li>
<li>
<a href="https://admin.uiogaming.no" rel="noreferrer">
{t("admin")}
</a>
</li>
</ul>
</div>
<div className={styles.orgInfo}>
<p>{t("orgNum")}: 925 719 153</p>
<address>Slemdalsveien 15, 0369 Oslo</address>
</div>
</div>
</footer>
)
}
export default Footer
@@ -1,6 +1,6 @@
.container {
background-color: #7C5CA8;
background-image: url("../../images/mustafa_blurred.png"); /* Placeholder image */
background-color: #7c5ca8;
background-image: url("/mustafa.png");
background-repeat: no-repeat;
background-size: cover;
@@ -8,24 +8,36 @@
padding: 1rem;
padding-bottom: 10vh;
height: 100vh;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
max-width: none;
text-align: center;
}
.content {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 1em;
max-width: none;
text-align: center;
background-color: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
padding: 2em;
margin-block: 2.5em;
border-radius: 1rem;
}
.container > p {
.content > p {
max-width: 40ch;
color: #130729;
font-size: 1.5rem;
}
.container > h1 {
.content > h1 {
color: #130729;
font-size: 4rem;
}
+24
View File
@@ -0,0 +1,24 @@
import { useTranslations } from "next-intl"
import Image from "next/image"
import * as styles from "./hero.module.css"
const Hero = () => {
const t = useTranslations("hero")
return (
<header id="hjem" className={styles.container}>
<div className={styles.content}>
<Image
src="/logo_2024_black_trans.png"
alt="UiO Gaming logo"
width={187}
height={256}
/>
<p>{t("text")}</p>
</div>
</header>
)
}
export default Hero
@@ -0,0 +1,25 @@
.container {
display: grid;
gap: 2rem;
justify-content: center;
place-items: center;
}
.image {
width: 100%;
aspect-ratio: 1 / 1;
}
.panoramaContainer {
width: 100%;
height: 400px;
background: #000000;
border-radius: 8px;
overflow: hidden;
position: relative;
}
.panoramaContainer > div {
width: 100% !important;
height: 100% !important;
}
+39
View File
@@ -0,0 +1,39 @@
"use client"
import { useTranslations } from "next-intl"
import { ReactPhotoSphereViewer } from "react-photo-sphere-viewer"
import * as styles from "./office.module.css"
const Office = () => {
const t = useTranslations("office")
return (
<section>
<h2>{t("title")}</h2>
<div className={styles.container}>
<p>{t("text")}</p>
<p>{t("tip")}</p>
<div
style={{
width: "100%",
height: "500px",
background: "#000000",
borderRadius: "8px",
overflow: "hidden",
}}
>
<ReactPhotoSphereViewer
src="/3D_office_2024_11_03.jpg"
height="100%"
width="100%"
defaultZoomLvl={0}
navbar={true}
/>
</div>
</div>
</section>
)
}
export default Office
-61
View File
@@ -1,61 +0,0 @@
import * as React from "react"
import * as styles from "./about.module.css"
import { graphql, useStaticQuery } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
import DiscordButton from "../items/discordButton"
import MemberButton from "../items/memberButton"
const About = () => {
const data = useStaticQuery(graphql`
query {
logo: file(relativePath: { eq: "about.jpg" }) {
childImageSharp {
gatsbyImageData(
placeholder: BLURRED
transformOptions: { fit: CONTAIN }
width: 650
)
}
}
}
`)
return (
<section>
<div className={styles.container}>
<div className="no-mobile">
<GatsbyImage
image={data.logo.childImageSharp.gatsbyImageData}
alt="Medlemmer som spiller brettspill"
/>
</div>
<div>
<h2 className="left-section-header">Hvem er vi?</h2>
<div>
<p>
UiO Gaming er en forening med hovedfokus på å danne et
inkluderende fellesskap rundt videospill og e-sport. Hos oss er
det enkelt å skaffe seg venner, ettersom alle har felles
interesser. Vi arrangerer ukentlige spillkvelder og fysiske
sosiale arrangementer, som for eksempel brettspillkvelder og
sosiale sammenkomster. Vår hovedkanal er en aktiv Discord-server
hvor man kan møte nye folk med felles interesser og game med andre
studenter.
</p>
<div className={styles.joinSection}>
<h3>Interessert?</h3>
<div className={styles.buttons}>
<MemberButton />
<DiscordButton />
</div>
</div>
</div>
</div>
</div>
</section>
)
}
export default About
-65
View File
@@ -1,65 +0,0 @@
import * as React from "react"
import * as styles from "./boardMembers.module.css"
import { graphql, useStaticQuery } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
const BoardMembers = () => {
const data = useStaticQuery(graphql`
query {
allSanityAuthor(
filter: { boardMember: { eq: true } }
sort: { fields: order }
) {
edges {
node {
_id
boardMember
email
name
role
image {
asset {
gatsbyImageData(width: 256, height: 256)
}
}
bio
}
}
}
}
`)
return (
<section>
<h2>Styret</h2>
<div className={styles.container}>
{data.allSanityAuthor.edges.map(({ node: member }) => (
<article key={member._id} className={styles.card}>
<div className={styles.header}>
<div
className={`circular-container ${styles.headerImageContainer}`}
>
<GatsbyImage
image={member.image.asset.gatsbyImageData}
className="circular"
alt={"Bilde av styemedlem, " + member.name}
/>
</div>
<div className={styles.info}>
<h3>{member.name}</h3>
<p>{member.role}</p>
</div>
</div>
<p>{member.bio}</p>
<div className={styles.footer}>
<p className="text-center">{member.email}</p>
</div>
</article>
))}
</div>
</section>
)
}
export default BoardMembers
-76
View File
@@ -1,76 +0,0 @@
import * as React from "react"
import * as styles from "./contact.module.css"
import {
FaYoutube,
FaTwitch,
FaFacebook,
FaGithub,
FaDiscord,
FaAt,
FaInstagram,
} from "react-icons/fa"
const Contact = () => (
<section>
<h2 className="section-center-header">Finn oss</h2>
<div className={styles.container}>
<div className={styles.item}>
<FaAt />
<a
href="mailto:styret@uiogaming.no"
target="_blank"
rel="noreferrer"
>
E-post
</a>
</div>
<div className={styles.item}>
<FaDiscord />
<a href="https://discord.uiogaming.no" target="_blank" rel="noreferrer">
Discord
</a>
</div>
<div className={styles.item}>
<FaFacebook />
<a
href="https://facebook.uiogaming.no"
target="_blank"
rel="noreferrer"
>
Facebook
</a>
</div>
<div className={styles.item}>
<FaInstagram />
<a
href="https://instagram.uiogaming.no"
target="_blank"
rel="noreferrer"
>
Instagram
</a>
</div>
<div className={styles.item}>
<FaYoutube />
<a href="https://youtube.uiogaming.no" target="_blank" rel="noreferrer">
YouTube
</a>
</div>
<div className={styles.item}>
<FaTwitch />
<a href="https://twitch.uiogaming.no" target="_blank" rel="noreferrer">
Twitch
</a>
</div>
<div className={styles.item}>
<FaGithub />
<a href="https://github.uiogaming.no" target="_blank" rel="noreferrer">
GitHub
</a>
</div>
</div>
</section>
)
export default Contact
-101
View File
@@ -1,101 +0,0 @@
import * as React from "react"
import * as styles from "./events.module.css"
import { Link, graphql, useStaticQuery } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
import moment from "moment-timezone"
import "moment/locale/nb"
import { FaMapMarkerAlt, FaCalendarAlt } from "react-icons/fa"
const Events = () => {
const data = useStaticQuery(graphql`
query {
placeholder: file(relativePath: { eq: "event.jpg" }) {
childImageSharp {
gatsbyImageData(
placeholder: BLURRED
transformOptions: { fit: COVER }
width: 650
)
}
}
allSanityEvent(sort: { fields: date, order: ASC }) {
edges {
node {
_id
title
location
date
slug {
current
}
description
}
}
}
}
`)
moment.locale("nb_NO")
const currentDate = moment().utc()
const filteredEvents = data.allSanityEvent.edges
.filter(({ node: event }) => {
return currentDate <= moment.utc(event.date)
})
.slice(0, 2)
const mapEvents = events => {
if (events.length !== 0) {
return events.map(({ node: event }) => (
<Link key={event._id} to={"event/" + event.slug.current}>
<article className={styles.card}>
<h3>{event.title}</h3>
<div>
<div className={styles.metadata}>
<FaMapMarkerAlt />
<p>{event.location}</p>
</div>
<div className={styles.metadata}>
<FaCalendarAlt />
<p>
{moment(event.date)
.tz("Europe/Oslo")
.format("dddd Do MMMM, [kl.] H:mm")}
</p>
</div>
</div>
<p>{event.description.slice(0, 120)}... <span className={styles.readMore}>Les mer</span></p>
</article>
</Link>
))
} else {
return (
<h3 className={styles.noEvents}>
Ingen kommende arrangementer
<br />
<br />
(っ˘̩╭╮˘̩)っ
</h3>
)
}
}
return (
<section className={styles.stretch}>
<div className={styles.container}>
<h2 className={styles.sectionHeader}>Hva skjer?</h2>
<div className={styles.events}>{mapEvents(filteredEvents)}</div>
<div className={"no-mobile " + styles.image}>
<GatsbyImage
image={data.placeholder.childImageSharp.gatsbyImageData}
alt="Medlem som spiller VR"
/>
</div>
</div>
</section>
)
}
export default Events
-68
View File
@@ -1,68 +0,0 @@
import * as React from "react"
import * as styles from "./footer.module.css"
import { graphql, useStaticQuery } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
const Footer = () => {
const data = useStaticQuery(graphql`
query {
logo: file(relativePath: { eq: "logo.jpg" }) {
childImageSharp {
gatsbyImageData(
placeholder: BLURRED
transformOptions: { fit: COVER }
width: 256
)
}
}
}
`)
return (
<footer>
<div className={styles.footer}>
<div>
<GatsbyImage
image={data.logo.childImageSharp.gatsbyImageData}
alt="UiO Gaming logo"
/>
</div>
<div className={styles.links}>
<ul className="remove-bullets">
<li>
<a href="https://vedtekter.uiogaming.no" rel="noreferrer">
Vedtekter
</a>
</li>
<li>
<a href="https://innmelding.uiogaming.no" rel="noreferrer">
Bli medlem
</a>
</li>
<li>
<a
href="https://github.com/UiO-Gaming/uiogaming-website/"
rel="noreferrer"
>
Kildekode
</a>
</li>
<li>
<a href="https://admin.uiogaming.no" rel="noreferrer">
Adminområdet
</a>
</li>
</ul>
</div>
<div className={styles.orgInfo}>
<p>Orgnr.: 925 719 153</p>
<address>Slemdalsveien 15, 0369 Oslo</address>
</div>
</div>
</footer>
)
}
export default Footer
-38
View File
@@ -1,38 +0,0 @@
import * as React from "react"
import * as styles from "./header.module.css"
import { graphql, useStaticQuery } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
const Header = () => {
const data = useStaticQuery(graphql`
query {
placeholder: file(relativePath: { eq: "logo.jpg" }) {
childImageSharp {
gatsbyImageData(
placeholder: BLURRED
transformOptions: { fit: CONTAIN }
width: 256
)
}
}
}
`)
return (
<header id="hjem" className={styles.container}>
<GatsbyImage
image={data.placeholder.childImageSharp.gatsbyImageData}
alt="UiO Gaming logo"
/>
<h1>UiO Gaming</h1>
<p>
En hobbyforening som ønsker å skape et sosialt miljø rundt gaming og
e-sport på UiO
</p>
</header>
)
}
export default Header
-30
View File
@@ -1,30 +0,0 @@
import * as React from "react"
import * as styles from "./office.module.css"
import ReactPannellum from "react-pannellum"
const config = {
autoLoad: true,
}
const Office = () => (
<section>
<h2>Kontoret</h2>
<div className={styles.container}>
<p>
Vi har et foreningskontor på Chateau Neuf der man kan bruke utstyret
vårt og henge med andre medlemmer!
</p>
<ReactPannellum
id="1"
sceneId="office"
config={config}
imageSource="/3D_office_2023_09_15.jpg"
style={{ width: "100%", aspectRatio: "1 / 1", background: "#000000" }} // yes, apparantly applying a css class to the component doesn't work
/>
</div>
</section>
)
export default Office
-11
View File
@@ -1,11 +0,0 @@
.container {
display: grid;
gap: 2rem;
justify-content: center;
place-items: center;
}
.image {
width: 100%;
aspect-ratio: 1 / 1;
}
-124
View File
@@ -1,124 +0,0 @@
import * as React from "react"
import PropTypes from "prop-types"
import { Helmet } from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"
function Seo({ description, lang, meta, imageURL, title, author }) {
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
title
description
author
url
}
}
}
`
)
const metaAuthor = author || site.siteMetadata.author
const metaDescription = description || site.siteMetadata.description
const defaultTitle = site.siteMetadata?.title
return (
<Helmet
htmlAttributes={{
lang,
}}
title={title ? title : defaultTitle}
titleTemplate={title ? `%s | ${defaultTitle}` : defaultTitle}
meta={[
{
name: `author`,
content: metaAuthor,
},
{
name: `description`,
content: metaDescription,
},
{
property: `og:site_name`,
content: site.siteMetadata.title,
},
{
property: `og:title`,
content: title ? title : defaultTitle,
},
{
property: `og:description`,
content: metaDescription,
},
{
property: `og:type`,
content: `website`,
},
{
property: `og:locale`,
content: `nb_NO`,
},
{
name: `twitter:creator`,
content: metaAuthor,
},
{
name: `twitter:title`,
content: title,
},
{
name: `twitter:description`,
content: metaDescription,
},
]
.concat(
imageURL
? [
{
property: "og:image",
content: imageURL,
},
{
name: "twitter:card",
content: "summary_large_image",
},
]
: [
{
property: "og:image",
content: `${site.siteMetadata.url}/logo.jpg`,
},
{
name: "twitter:card",
content: "summary",
},
]
)
.concat(meta)}
>
<script
async
defer
data-website-id="c375ccba-0b3e-4bf0-a4c9-165bd9b0176c"
src="https://umami.uiogaming.no/umami.js"
></script>
</Helmet>
)
}
Seo.defaultProps = {
lang: `no`,
meta: [],
description: ``,
}
Seo.propTypes = {
description: PropTypes.string,
lang: PropTypes.string,
meta: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string,
}
export default Seo
+69
View File
@@ -0,0 +1,69 @@
import { Metadata, Viewport } from "next"
type SeoProps = {
title?: string
description?: string
imageURL?: string
author?: string
}
export function generateSeoMetadata({
title,
description,
imageURL,
author,
}: SeoProps): Metadata {
const defaultTitle = "UiO Gaming"
const metaDescription =
description ||
"En hobbyforening som ønsker skape et sosialt miljö rundt gaming og e-sport på UiO!"
const siteUrl = "https://uiogaming.no"
const themeColor = "#7d36e7"
return {
title: title ? `${title} | ${defaultTitle}` : defaultTitle,
description: metaDescription,
authors: author ? [{ name: author }] : [{ name: "UiO Gaming" }],
openGraph: {
title: title || defaultTitle,
description: metaDescription,
type: "website",
locale: "no",
siteName: defaultTitle,
images: imageURL
? [
{
url: imageURL,
width: 1200,
height: 630,
alt: title || defaultTitle,
},
]
: [
{
url: `${siteUrl}/logo_2024_neon_trans.png`,
alt: defaultTitle,
},
],
},
twitter: {
card: imageURL ? "summary_large_image" : "summary",
title: title || defaultTitle,
description: metaDescription,
images: imageURL ? [imageURL] : [`${siteUrl}/logo_2024_neon_trans.png`],
},
other: {
"theme-color": themeColor,
"msapplication-TileColor": themeColor,
"msapplication-navbutton-color": themeColor,
"apple-mobile-web-app-status-bar-style": "black-translucent",
},
}
}
export function generateSeoViewport(): Viewport {
return {
themeColor: "#7d36e7",
colorScheme: "dark",
}
}
File renamed without changes.
+36
View File
@@ -0,0 +1,36 @@
"use client"
import { useTranslations } from "next-intl"
import { FaArrowLeft } from "react-icons/fa"
import * as styles from "./back.module.css"
const Back = () => {
const t = useTranslations("buttons")
const handleBack = () => {
const currentDomain = window.location.hostname
const referrer = document.referrer
const isSameDomain =
referrer && new URL(referrer).hostname === currentDomain
if (window.history.length > 1 && isSameDomain) {
window.history.back()
} else {
window.location.href = "/"
}
}
return (
<button
className={`${styles.container} ${styles.button}`}
onClick={handleBack}
>
<FaArrowLeft />
<p>{t("back")}</p>
</button>
)
}
export default Back
@@ -1,12 +1,12 @@
.button {
display: flex;
justify-content: center;
justify-content: center;
align-items: center;
font-size: 1.6rem;
background-color: #5865F2;
font-size: 1.6rem;
background-color: #5865f2;
padding: 0.5em 1em;
border-radius: 999em;
border-radius: 999em;
color: var(--text-color);
}
@@ -1,4 +1,3 @@
import * as React from "react"
import * as styles from "./discordButton.module.css"
const DiscordButton = () => (
+20
View File
@@ -0,0 +1,20 @@
.button {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
font-size: 1.6rem;
background-color: var(--main-color);
text-overflow: ellipsis;
white-space: nowrap;
padding: 0.5em 1em;
border-radius: 999em;
color: var(--text-color);
}
.button:hover {
color: white;
background-color: #a76cff;
}
+19
View File
@@ -0,0 +1,19 @@
import { useTranslations } from "next-intl"
import * as styles from "./memberButton.module.css"
const MemberButton = () => {
const t = useTranslations("common")
return (
<a
href="https://innmelding.uiogaming.no"
target="_blank"
rel="noreferrer"
className={styles.button}
>
{t("join")}
</a>
)
}
export default MemberButton
+78
View File
@@ -0,0 +1,78 @@
.button {
display: flex;
align-items: center;
gap: 0.5em;
justify-content: center;
font-size: 1.6rem;
background-color: #cccccc;
width: 100%;
padding: 0.5em 1em;
border: 0;
border-radius: 999em;
color: black;
}
.button:hover {
cursor: pointer;
color: black;
background-color: #e6e6e6;
}
.dropdownContainer {
position: relative;
display: inline-block;
width: 100%;
}
.dropdown {
display: flex;
flex-direction: column;
position: absolute;
top: 100%;
left: 0;
min-width: 100%;
border-radius: 10px;
}
.dropdownButton {
width: 100%;
color: black;
background-color: #cccccc;
border: none;
border-radius: 999em !important;
cursor: pointer;
outline: none;
border-radius: 10px;
text-align: left;
font-size: 1.6rem;
box-shadow: var(--shadow);
padding: 0.5em 1em;
padding-right: 2em;
}
.dropdownButton:hover {
color: black;
background-color: #e6e6e6;
}
.arrowUp,
.arrowDown {
border: solid black;
border-width: 0 2px 2px 0;
display: inline-block;
padding: 3px;
margin-left: 5px;
transition: transform 0.1s ease-in-out;
}
.arrowDown {
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
}
.arrowUp {
transform: rotate(-135deg);
-webkit-transform: rotate(-135deg);
}
+54
View File
@@ -0,0 +1,54 @@
"use client"
import { useState } from "react"
import { useLocale, useTranslations } from "next-intl"
import * as styles from "./otherButton.module.css"
const OtherButton = () => {
const t = useTranslations("about")
const tCommon = useTranslations("common")
const locale = useLocale()
const [isOpen, setIsOpen] = useState(false)
const toggleDropdown = () => setIsOpen(!isOpen)
const statutesUrl =
locale === "en"
? "https://statutes.uiogaming.no"
: "https://vedtekter.uiogaming.no"
const links = [
{ url: statutesUrl, label: tCommon("statutes") },
{ url: "https://klage.uiogaming.no", label: tCommon("complaint") },
{
url: "https://github.com/UiO-Gaming/dokumenter",
label: tCommon("documents"),
},
]
return (
<div className={styles.dropdownContainer}>
<button onClick={toggleDropdown} className={styles.button}>
{t("other")}{" "}
<span className={isOpen ? styles.arrowUp : styles.arrowDown}></span>
</button>
{isOpen && (
<div className={styles.dropdown}>
{links.map((link, index) => (
<button
key={index}
onClick={() => window.open(link.url, "_blank")}
className={styles.dropdownButton}
style={{ marginTop: index === 0 ? "8px" : "4px" }}
>
{link.label}
</button>
))}
</div>
)}
</div>
)
}
export default OtherButton
+50
View File
@@ -0,0 +1,50 @@
"use client"
import Script from "next/script"
interface UmamiAnalyticsProps {
websiteId: string
src?: string
domains?: string[]
}
export default function UmamiAnalytics({
websiteId,
src = "https://analytics.umami.is/script.js",
domains,
}: UmamiAnalyticsProps) {
if (!websiteId) {
console.warn("Umami: Website ID is required")
return null
}
// Only load analytics in production or if explicitly enabled
if (process.env.NODE_ENV !== "production") {
return null
}
// Domain validation (client-side check as additional layer)
if (domains && typeof window !== "undefined") {
const currentDomain = window.location.hostname
const isAllowedDomain = domains.some(
domain => currentDomain === domain || currentDomain.endsWith(`.${domain}`)
)
if (!isAllowedDomain) {
console.warn(
`Umami: Domain ${currentDomain} not in allowed list:`,
domains
)
return null
}
}
return (
<Script
src={src}
data-website-id={websiteId}
data-domains={domains?.join(",")}
strategy="afterInteractive"
/>
)
}
+12
View File
@@ -0,0 +1,12 @@
import { getRequestConfig } from "next-intl/server"
import { cookies } from "next/headers"
export default getRequestConfig(async () => {
const cookieStore = await cookies()
const locale = cookieStore.get("locale")?.value || "no"
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default,
}
})
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 519 KiB

+29
View File
@@ -0,0 +1,29 @@
interface AnalyticsConfig {
websiteId: string
src: string
domains: string[]
enabled: boolean
}
export function getAnalyticsConfig(): AnalyticsConfig | null {
const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID
const src =
process.env.NEXT_PUBLIC_UMAMI_SRC || "https://analytics.umami.is/script.js"
if (!websiteId) {
return null
}
// Define allowed domains
const domains = ["uiogaming.no", "www.uiogaming.no"]
// Additional environment-based checks
const enabled = process.env.NODE_ENV === "production"
return {
websiteId,
src,
domains,
enabled,
}
}
+10
View File
@@ -0,0 +1,10 @@
import imageUrlBuilder from "@sanity/image-url"
import type { SanityImageSource } from "@sanity/image-url/lib/types/types"
import { sanityClient } from "./sanity"
const builder = imageUrlBuilder(sanityClient)
export function urlFor(source: SanityImageSource) {
return builder.image(source)
}
+97
View File
@@ -0,0 +1,97 @@
import { createClient } from "@sanity/client"
export const sanityClient = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,
apiVersion: "2025-02-19",
useCdn: true,
})
export async function getBlogPost(slug: string) {
const query = `
*[_type == "post" && slug.current == $slug][0] {
_id,
_createdAt,
title,
author-> {
name,
image {
asset-> {
_id,
url
}
}
},
mainImage {
asset-> {
_id,
url
}
},
excerpt,
body
}
`
return sanityClient.fetch(
query,
{ slug },
{
cache: "force-cache",
next: {
tags: ["blog-posts", `post-${slug}`],
revalidate: 3600, // 1 hour
},
}
)
}
export async function getEvent(slug: string) {
const query = `
*[_type == "event" && slug.current == $slug][0] {
_id,
title,
location,
date,
slug,
description
}
`
return sanityClient.fetch(
query,
{ slug },
{
cache: "force-cache",
next: {
tags: ["events", `event-${slug}`],
revalidate: 3600, // 1 hour
},
}
)
}
export async function getAllEvents() {
const query = `
*[_type == "event"] | order(date asc) {
_id,
title,
location,
date,
slug,
description
}
`
return sanityClient.fetch(
query,
{},
{
cache: "force-cache",
next: {
tags: ["events"],
revalidate: 1800, // 30 minutes
},
}
)
}
-27
View File
@@ -1,27 +0,0 @@
import * as React from "react"
import * as styles from "./404.module.css"
import { Link } from "gatsby"
import Layout from "../components/layout"
import Seo from "../components/seo"
const NotFoundPage = () => (
<Layout>
<Seo title="404: Not found" />
<div className={styles.container}>
<h1 className={styles.centeredText}>404: Siden finnes ikke</h1>
<p className={styles.centeredText}>
Du har nådd en blindvei, men ikke på Blindern{" "}
<span role="img" aria-label="eyes">
👀
</span>
</p>
<Link to="/" className={`${styles.centeredText} ${styles.homeLink}`}>
Tilbake til hjemmesiden
</Link>
</div>
</Layout>
)
export default NotFoundPage
-80
View File
@@ -1,80 +0,0 @@
import * as React from "react"
import * as styles from "./blog.module.css"
import { graphql, Link } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import Seo from "../components/seo"
import Back from "../components/items/back"
export const query = graphql`
query {
allSanityPost(sort: { fields: _createdAt, order: DESC }) {
edges {
node {
title
_createdAt(formatString: "DD. MMMM, YYYY", locale: "nb_NO")
_id
slug {
current
}
excerpt
author {
name
image {
asset {
gatsbyImageData(width: 40, height: 40)
}
}
}
mainImage {
asset {
gatsbyImageData(aspectRatio: 1.618)
}
}
}
}
}
}
`
const BlogPage = ({ data }) => (
<Layout>
<Seo
title="Blog"
description="Nyheter om hva som skjer i foreningen og skriverier om alt og ingenting"
/>
<div className={styles.container}>
<div className={styles.back}>
<Back />
</div>
<div className={styles.articleContainer}>
{data.allSanityPost.edges.map(post => (
<Link to={post.node.slug.current} className={styles.link}>
<article className={styles.card}>
<GatsbyImage
image={post.node.mainImage.asset.gatsbyImageData}
className={styles.headerImage}
/>
<div className={styles.cardContent}>
<h2 className={styles.header}>{post.node.title}</h2>
<p className={styles.published}>{post.node._createdAt}</p>
<div className={styles.author}>
<GatsbyImage
className={`circular ${styles.authorImage}`}
alt={"Forfatteren av innlegget, " + post.node.author.name}
image={post.node.author.image.asset.gatsbyImageData}
/>
<p>{post.node.author.name}</p>
</div>
<p>{post.node.excerpt}</p>
</div>
</article>
</Link>
))}
</div>
</div>
</Layout>
)
export default BlogPage
-66
View File
@@ -1,66 +0,0 @@
.container {
display: grid;
gap: 2rem;
padding: 1.25rem;
}
.back {
justify-self: start;
}
.articleContainer {
display: grid;
grid-template-columns: auto;
place-items: center;
align-items: stretch;
gap: 3rem;
}
.card {
display: grid;
max-width: 50ch;
box-shadow: 0 6px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2);
}
.headerImage {
max-height: 50vh;
}
.cardContent {
background-color: var(--secondary-background-color);
padding: 2rem;
}
.published {
font-weight: 100;
}
.header {
font-size: 2.25rem;
}
.author {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: 0.5em;
font-weight: 100;
margin-block: 1rem;
}
.authorImage {
border: 2px solid white;
}
.link {
color: white;
}
.link h2 {
text-decoration: underline;
text-decoration-color: var(--link-color);
}
.link:hover h2 {
text-decoration-color: var(--link-color-hover);
}
@media (min-width: 60em) {
.container {
padding: 2rem;
}
.articleContainer {
grid-template-columns: auto auto;
}
}
-79
View File
@@ -1,79 +0,0 @@
import * as React from "react"
import * as styles from "./blogPost.module.css"
import { graphql } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
import Seo from "../components/seo"
import BlockContent from "@sanity/block-content-to-react"
import Back from "../components/items/back"
export const query = graphql`
query($id: String) {
sanityPost(_id: { eq: $id }) {
_createdAt(formatString: "DD. MMMM, YYYY", locale: "nb_NO")
title
author {
name
image {
asset {
gatsbyImageData(width: 50, height: 50)
}
}
}
mainImage {
asset {
url
gatsbyImageData
}
}
excerpt
_rawBody(resolveReferences: { maxDepth: 10 })
}
}
`
const BlogPost = ({ data }) => {
return (
<>
<Seo
title={data.sanityPost.title}
imageURL={data.sanityPost.mainImage.asset.url}
description={data.sanityPost.excerpt}
author={data.sanityPost.author.name}
/>
<div className={styles.container}>
<div className={styles.backButton}>
<Back />
</div>
<article className={styles.article}>
<header className={styles.header}>
<GatsbyImage
image={data.sanityPost.mainImage.asset.gatsbyImageData}
className={styles.headerImage}
/>
<h1 className={styles.title}>{data.sanityPost.title}</h1>
<p className={styles.excerpt}>{data.sanityPost.excerpt}</p>
<div className={styles.author}>
<GatsbyImage
className="circular"
alt={"Forfatteren av innlegget, " + data.sanityPost.author.name}
image={data.sanityPost.author.image.asset.gatsbyImageData}
/>
<div className={styles.authorNameSection}>
<p>{data.sanityPost.author.name}</p>
<p>{data.sanityPost._createdAt}</p>
</div>
</div>
</header>
<BlockContent
className={styles.content}
blocks={data.sanityPost._rawBody}
/>
</article>
</div>
</>
)
}
export default BlogPost
-61
View File
@@ -1,61 +0,0 @@
import * as React from "react"
import * as styles from "./eventPost.module.css"
import { graphql } from "gatsby"
import moment from "moment-timezone"
import Seo from "../components/seo"
import Back from "../components/items/back"
import { FaMapMarkerAlt, FaCalendarAlt } from "react-icons/fa"
export const query = graphql`
query($id: String) {
sanityEvent(_id: { eq: $id }) {
title
location
date
slug {
current
}
description
}
}
`
moment.locale("nb_NO")
const Event = ({ data }) => {
const time = moment(data.sanityEvent.date)
.tz("Europe/Oslo")
.format("dddd Do MMMM, [kl.] H:mm")
return (
<>
<Seo
title={data.sanityEvent.title}
description={`Tidspunkt: ${time}\nSted: ${data.sanityEvent.location}\n\n${data.sanityEvent.description}`}
/>
<div className={styles.container}>
<Back />
<div className={styles.event}>
<h1>{data.sanityEvent.title}</h1>
<div className={styles.metadata}>
<FaMapMarkerAlt />
<p>{data.sanityEvent.location}</p>
</div>
<div className={styles.metadata}>
<FaCalendarAlt />
<p>
{moment(data.sanityEvent.date)
.tz("Europe/Oslo")
.format("dddd Do MMMM, [kl.] H:mm")}
</p>
</div>
<p className={styles.description}>{data.sanityEvent.description}</p>
</div>
</div>
</>
)
}
export default Event
-28
View File
@@ -1,28 +0,0 @@
import * as React from "react"
import "../index.css"
import Layout from "../components/layout"
import Seo from "../components/seo"
import Header from "../components/sections/header"
import About from "../components/sections/about"
import Events from "../components/sections/events"
import Office from "../components/sections/office"
import BoardMembers from "../components/sections/boardMembers"
import Contact from "../components/sections/contact"
import Footer from "../components/sections/footer"
const IndexPage = () => (
<Layout>
<Seo />
<Header />
<About />
<Events />
<Office />
<BoardMembers />
<Contact />
<Footer />
</Layout>
)
export default IndexPage
-33
View File
@@ -1,33 +0,0 @@
import * as React from "react"
import * as styles from "./room.module.css"
import Layout from "../components/layout"
import Seo from "../components/seo"
const RoomPage = () => (
<Layout>
<Seo
title="Foreningsrom"
description="Kalender og booking av foreningsrom"
/>
<div className={styles.container}>
<iframe
src="https://calendar.google.com/calendar/embed?height=600&wkst=2&bgcolor=%23D50000&ctz=Europe%2FBerlin&showPrint=0&showTabs=1&showCalendars=0&showTz=1&mode=WEEK&showTitle=0&title=Rombooking&src=N2YxM2IzYWIyNDA3NTEyZGU3ZDk2MTdhZmNkZmI2ZjFmZjg1YzU3N2QwODNjZGIxNzA3MWRiNTdlMmQ3ZDM1NEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t&color=%233F51B5"
width="800"
height="600"
frameborder="0"
scrolling="no"
></iframe>
<a
href="https://forms.gle/1aj4yKXpbb788sWo8"
target="_blank"
rel="noreferrer"
className={styles.button}
>
Book rommet
</a>
</div>
</Layout>
)
export default RoomPage
-22
View File
@@ -1,22 +0,0 @@
.container {
display: grid;
place-items: center;
gap: 1rem;
}
.button {
display: flex;
justify-content: center;
font-size: 1.6rem;
background-color: var(--main-color);
padding: 0.5em 1em;
border-radius: 999em;
color: var(--text-color);
}
.button:hover {
color: white;
background-color: rgb(255, 62, 52);
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

+35
View File
@@ -0,0 +1,35 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@lib/*": ["./src/lib/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"global.d.ts"
],
"exclude": ["node_modules"]
}
-31
View File
@@ -1,31 +0,0 @@
const express = require("express")
const cors = require("cors")
const { exec } = require("child_process")
const app = express()
const frontendURL = process.env.FRONTEND_URL || "https://uiogaming.no"
const port = process.env.PORT || 9001
app.use(cors({ origin: frontendURL }))
app.get("/", (req, res) => {
res.send("API is running! Yeet!")
})
app.post("/build", (req, res) => {
exec("cd ../ && gatsby build", (error, stdout, stderr) => {
if (error) {
console.log(stderr.message)
console.log(error.message)
return
}
console.log(stdout)
})
res.send("Success!")
})
app.listen(port, () => {
console.log(`LIVE! http://0.0.0.0:${port}`)
})
-17
View File
@@ -1,17 +0,0 @@
{
"name": "webhook-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.17.1"
}
}
-381
View File
@@ -1,381 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
dependencies:
mime-types "~2.1.24"
negotiator "0.6.2"
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
body-parser@1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
dependencies:
bytes "3.1.0"
content-type "~1.0.4"
debug "2.6.9"
depd "~1.1.2"
http-errors "1.7.2"
iconv-lite "0.4.24"
on-finished "~2.3.0"
qs "6.7.0"
raw-body "2.4.0"
type-is "~1.6.17"
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
content-disposition@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
dependencies:
safe-buffer "5.1.2"
content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
cookie@0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
express@^4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
dependencies:
accepts "~1.3.7"
array-flatten "1.1.1"
body-parser "1.19.0"
content-disposition "0.5.3"
content-type "~1.0.4"
cookie "0.4.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "~1.1.2"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "~1.1.2"
fresh "0.5.2"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "~2.3.0"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
proxy-addr "~2.0.5"
qs "6.7.0"
range-parser "~1.2.1"
safe-buffer "5.1.2"
send "0.17.1"
serve-static "1.14.1"
setprototypeof "1.1.1"
statuses "~1.5.0"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
finalhandler@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
dependencies:
debug "2.6.9"
encodeurl "~1.0.2"
escape-html "~1.0.3"
on-finished "~2.3.0"
parseurl "~1.3.3"
statuses "~1.5.0"
unpipe "~1.0.0"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
http-errors@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
dependencies:
depd "~1.1.2"
inherits "2.0.3"
setprototypeof "1.1.1"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
http-errors@~1.7.2:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
dependencies:
depd "~1.1.2"
inherits "2.0.4"
setprototypeof "1.1.1"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
inherits@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ipaddr.js@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
mime-db@1.49.0:
version "1.49.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed"
integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==
mime-types@~2.1.24:
version "2.1.32"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5"
integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==
dependencies:
mime-db "1.49.0"
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
object-assign@^4:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
dependencies:
ee-first "1.1.1"
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
proxy-addr@~2.0.5:
version "2.0.7"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
dependencies:
forwarded "0.2.0"
ipaddr.js "1.9.1"
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
dependencies:
bytes "3.1.0"
http-errors "1.7.2"
iconv-lite "0.4.24"
unpipe "1.0.0"
safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
dependencies:
debug "2.6.9"
depd "~1.1.2"
destroy "~1.0.4"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "~1.7.2"
mime "1.6.0"
ms "2.1.1"
on-finished "~2.3.0"
range-parser "~1.2.1"
statuses "~1.5.0"
serve-static@1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.17.1"
setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
type-is@~1.6.17, type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
dependencies:
media-typer "0.3.0"
mime-types "~2.1.24"
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
vary@^1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
-11688
View File
File diff suppressed because it is too large. Load diff