diff --git a/package-lock.json b/package-lock.json index 93ccab3..9c38d94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "nibmcs.org", "version": "0.1.0", "dependencies": { + "@emailjs/browser": "^3.11.0", "@nextui-org/react": "^2.1.13", "@sanity/image-url": "^1.0.2", "@sanity/vision": "^3.16.7", @@ -16,8 +17,11 @@ "framer-motion": "^10.16.4", "next": "^13.5.6", "next-sanity": "^5.5.4", + "next-themes": "^0.2.1", "react": "latest", "react-dom": "latest", + "react-icons": "^4.11.0", + "react-toastify": "^9.1.3", "sanity": "^3.16.7", "styled-components": "5.2" }, @@ -744,6 +748,14 @@ "react": ">=16.8.0" } }, + "node_modules/@emailjs/browser": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@emailjs/browser/-/browser-3.11.0.tgz", + "integrity": "sha512-RkY3FKZ3fPdK++OeF46mRTFpmmQWCHUVHZH209P3NE4D5sg2Atg7S2wa3gw5062Gl4clt4Wn5SyC4WhlVZC5pA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@emotion/is-prop-valid": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", @@ -10200,6 +10212,16 @@ "styled-components": "^5.2.0 || ^6.0.0" } }, + "node_modules/next-themes": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", + "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", + "peerDependencies": { + "next": "*", + "react": "*", + "react-dom": "*" + } + }, "node_modules/node-abi": { "version": "3.47.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", @@ -11406,6 +11428,14 @@ } } }, + "node_modules/react-icons": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz", + "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -11542,6 +11572,18 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-toastify": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", + "integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 9bb0f52..ba1e3cd 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@emailjs/browser": "^3.11.0", "@nextui-org/react": "^2.1.13", "@sanity/image-url": "^1.0.2", "@sanity/vision": "^3.16.7", @@ -17,8 +18,11 @@ "framer-motion": "^10.16.4", "next": "^13.5.6", "next-sanity": "^5.5.4", + "next-themes": "^0.2.1", "react": "latest", "react-dom": "latest", + "react-icons": "^4.11.0", + "react-toastify": "^9.1.3", "sanity": "^3.16.7", "styled-components": "5.2" }, diff --git a/public/NCS-logo-color.png b/public/NCS-logo-color.png new file mode 100644 index 0000000..328f1bd Binary files /dev/null and b/public/NCS-logo-color.png differ diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..a104f02 Binary files /dev/null and b/public/logo.png differ diff --git a/src/app/about/layout.tsx b/src/app/about/layout.tsx new file mode 100644 index 0000000..d5f6271 --- /dev/null +++ b/src/app/about/layout.tsx @@ -0,0 +1,13 @@ +export default function AboutLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx new file mode 100644 index 0000000..9e4b155 --- /dev/null +++ b/src/app/about/page.tsx @@ -0,0 +1,7 @@ +export default function AboutPage() { + return ( +
+

About

+
+ ); +} diff --git a/src/app/blog/layout.tsx b/src/app/blog/layout.tsx new file mode 100644 index 0000000..45deef1 --- /dev/null +++ b/src/app/blog/layout.tsx @@ -0,0 +1,13 @@ +export default function BlogLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx new file mode 100644 index 0000000..9990b69 --- /dev/null +++ b/src/app/blog/page.tsx @@ -0,0 +1,7 @@ +export default function BlogPage() { + return ( +
+

Blog

+
+ ); +} diff --git a/src/app/contact/layout.tsx b/src/app/contact/layout.tsx new file mode 100644 index 0000000..c3c01b7 --- /dev/null +++ b/src/app/contact/layout.tsx @@ -0,0 +1,13 @@ +export default function ContactLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx new file mode 100644 index 0000000..6c5cb02 --- /dev/null +++ b/src/app/contact/page.tsx @@ -0,0 +1,156 @@ +'use client'; + +import React, { useRef } from 'react'; +import { motion } from 'framer-motion'; +import { BsArrowRight } from 'react-icons/bs'; +import { + HiOutlineMapPin, + HiOutlineEnvelope, + HiOutlinePhone, +} from 'react-icons/hi2'; +import { ToastContainer, toast } from 'react-toastify'; +import { fadeIn } from '../../utils/variants'; +import emailjs from '@emailjs/browser'; +import 'react-toastify/dist/ReactToastify.css'; +import Map from '../../components/map'; +import Box from '../../components/box'; + +export default function ContactPage() { + const ref: any = useRef(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + const formData = new FormData(ref.current); + const requiredFields = ['user_name', 'user_email', 'subject', 'message']; + let isFormValid = true; + + requiredFields.forEach((field) => { + const value = formData.get(field); + if (!value || '') { + toast.error(`Please fill in ${field} field.`, { + theme: 'dark', + }); + isFormValid = false; + } + }); + + if (!isFormValid) { + return; + } + + emailjs + .sendForm( + 'service_ap4wbtw', + 'template_grf4xdq', + ref.current, + 'dStWJXq6rdaw4xHWs' + ) + .then( + () => { + toast.success('Message sent successfully!', { + theme: 'dark', + }); + + ref.current.reset(); + }, + () => { + toast.error('Something went wrong!', { + theme: 'dark', + }); + } + ); + }; + + return ( + <> +
+
+
+ + Let's connect. + + + +
+ + +
+ + + + +
+
+
+
+
+
+ +
+ + } + name={'Address'} + para={'120/5 Vidya Mawatha, Colombo 00700'} + /> + } + name={'Email'} + para={'info@nibmcs.org'} + /> + } name={'Phone'} para={'+94712691003'} /> + +
+ + ); +} diff --git a/src/app/events/layout.tsx b/src/app/events/layout.tsx new file mode 100644 index 0000000..e119668 --- /dev/null +++ b/src/app/events/layout.tsx @@ -0,0 +1,13 @@ +export default function EventsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/src/app/events/page.tsx b/src/app/events/page.tsx new file mode 100644 index 0000000..da87c79 --- /dev/null +++ b/src/app/events/page.tsx @@ -0,0 +1,7 @@ +export default function EventsPage() { + return ( +
+

Events

+
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 8c106d8..049bee4 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,12 +1,25 @@ -import "./globals.css"; -import type { Metadata } from "next"; -import { Providers } from "./providers"; -import { Analytics } from "@vercel/analytics/react"; +import './globals.css'; +import type { Metadata } from 'next'; +import { Providers } from './providers'; +import { siteConfig } from '@/config/site'; +import clsx from 'clsx'; +import { fontSans } from '@/config/fonts'; +import { Navbar } from '@/components/navbar'; +import Footer from '@/components/Footer'; +import Social from '@/components/social'; export const metadata: Metadata = { - title: "NIBM Computing Society", - description: - "We are a student community organization for the NIBM - Sri Lanka, established with the aim of enabling us to learn, share, and build our professions.", + title: siteConfig.name, + description: siteConfig.description, + themeColor: [ + { media: '(prefers-color-scheme: light)', color: 'white' }, + { media: '(prefers-color-scheme: dark)', color: 'black' }, + ], + icons: { + icon: '/favicon.ico', + shortcut: '/favicon-16x16.png', + apple: '/apple-touch-icon.png', + }, }; export default function RootLayout({ @@ -15,10 +28,23 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - - {children} - + + + +
+ + +
+ {children} +
+
+
+
); diff --git a/src/app/leaderboard/layout.tsx b/src/app/leaderboard/layout.tsx new file mode 100644 index 0000000..953bc91 --- /dev/null +++ b/src/app/leaderboard/layout.tsx @@ -0,0 +1,13 @@ +export default function LeaderBoardLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/src/app/leaderboard/page.tsx b/src/app/leaderboard/page.tsx new file mode 100644 index 0000000..c7bbde0 --- /dev/null +++ b/src/app/leaderboard/page.tsx @@ -0,0 +1,7 @@ +export default function LeaderBoardPage() { + return ( +
+

Leaderboard

+
+ ); +} diff --git a/src/app/membership/layout.tsx b/src/app/membership/layout.tsx new file mode 100644 index 0000000..465fb25 --- /dev/null +++ b/src/app/membership/layout.tsx @@ -0,0 +1,13 @@ +export default function MembershipLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/src/app/membership/page.tsx b/src/app/membership/page.tsx new file mode 100644 index 0000000..22c803d --- /dev/null +++ b/src/app/membership/page.tsx @@ -0,0 +1,7 @@ +export default function MembershipPage() { + return ( +
+

Membership

+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index e165fb3..d0f6866 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,9 +1,13 @@ import React from "react"; +import { siteConfig } from "@/config/site"; +import { Logo } from "@/components/icons"; export default function Home() { return ( -
-
Coming Soon
+
+
{siteConfig.name}
+ +
Home Page
); -} \ No newline at end of file +} diff --git a/src/app/providers.tsx b/src/app/providers.tsx index be02117..08e75ea 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -1,8 +1,28 @@ -'use client'; +// "use client"; -import './globals.css'; -import { NextUIProvider } from '@nextui-org/react'; +// import "./globals.css"; +// import { NextUIProvider } from '@nextui-org/react'; -export function Providers({ children }: { children: React.ReactNode }) { - return {children}; +// export function Providers({ children }: { children: React.ReactNode }) { +// return {children}; +// } + +"use client"; + +import * as React from "react"; +import { NextUIProvider } from "@nextui-org/system"; +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { ThemeProviderProps } from "next-themes/dist/types"; + +export interface ProvidersProps { + children: React.ReactNode; + themeProps?: ThemeProviderProps; +} + +export function Providers({ children, themeProps }: ProvidersProps) { + return ( + + {children} + + ); } diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000..edbedfc --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,11 @@ +import { siteConfig } from "@/config/site"; + +export default function Footer() { + return ( +
+

+ © 2023 {siteConfig.name}. All rights reserved. +

+
+ ); +} diff --git a/src/components/box.tsx b/src/components/box.tsx new file mode 100644 index 0000000..5be63a8 --- /dev/null +++ b/src/components/box.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +const Box = ({ icon, name, para }: any) => { + return ( +
+
+
{icon}
+

+ {name} +

+

{para}

+
+
+ ); +}; + +export default Box; diff --git a/src/components/icons.tsx b/src/components/icons.tsx new file mode 100644 index 0000000..5697f69 --- /dev/null +++ b/src/components/icons.tsx @@ -0,0 +1,76 @@ +import { + RiFacebookLine, + RiGithubLine, + RiInstagramLine, + RiLinkedinLine, + RiTwitterXLine, +} from 'react-icons/ri'; +import { MdDarkMode, MdLightMode } from 'react-icons/md'; +import Image from 'next/image'; + +const iconSize = 18; +const className = 'text-default-500 hover:text-[#1E50FF] transition-all duration-300 ease-in-out'; + +interface ThemedImageProps { + className?: string; + width?: number; + height?: number; + priority?: boolean; +} + +export const Logo = ({ + className, + width = 200, + height = 112.5, + priority = false, +}: ThemedImageProps) => ( + <> + {/* When the theme is dark */} + NCS LOGO + + {/* When the theme is light */} + NCS LOGO + +); + +export const FacebookIcon = () => { + return ; +}; + +export const InstagramIcon = () => { + return ; +}; + +export const TwitterIcon = () => { + return ; +}; + +export const GithubIcon = () => { + return ; +}; + +export const LinkedinIcon = () => { + return ; +}; + +export const DarkModeIcon = () => { + return ; +}; + +export const LightModeIcon = () => { + return ; +}; diff --git a/src/components/map.tsx b/src/components/map.tsx new file mode 100644 index 0000000..1f85dd3 --- /dev/null +++ b/src/components/map.tsx @@ -0,0 +1,23 @@ +import { motion } from 'framer-motion'; +import { fadeIn } from '../utils/variants'; + +export default function Map() { + return ( +
+ +
+ ); +} diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx new file mode 100644 index 0000000..fd0444e --- /dev/null +++ b/src/components/navbar.tsx @@ -0,0 +1,136 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { + Navbar as NextUINavbar, + NavbarContent, + NavbarMenu, + NavbarMenuToggle, + NavbarBrand, + NavbarItem, + NavbarMenuItem, +} from '@nextui-org/navbar'; +import { Button } from '@nextui-org/react'; +import { Link } from '@nextui-org/link'; +import { link as linkStyles } from '@nextui-org/theme'; +import { siteConfig } from '@/config/site'; +import NextLink from 'next/link'; +import clsx from 'clsx'; +import { usePathname } from 'next/navigation'; +import { Logo } from '@/components/icons'; + +export const Navbar = () => { + const [scrolling, setScrolling] = useState(false); + const pathname = usePathname(); + + useEffect(() => { + const handleScroll = () => { + if (window.scrollY > 50) { + setScrolling(true); + } else { + setScrolling(false); + } + }; + + window.addEventListener('scroll', handleScroll); + + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, []); + + return ( + + + + + + + + + + +
    + {siteConfig.navItems.map((item) => ( + + + {item.label} + + + ))} +
+
+ + + + + + + + + + + + +
+ {siteConfig.navItems.map((item, index) => ( + + + {item.label} + + + ))} + + + + Membership + + +
+
+
+ ); +}; diff --git a/src/components/social.tsx b/src/components/social.tsx new file mode 100644 index 0000000..bf7cc91 --- /dev/null +++ b/src/components/social.tsx @@ -0,0 +1,43 @@ +'use client'; + +import { + RiInstagramLine, + RiFacebookLine, + RiGithubLine, + RiLinkedinLine, + RiTwitterXLine, +} from 'react-icons/ri'; +import Link from 'next/link'; + +export const socialData = [ + { path: 'https://instagram.com/nibmcs', icon: }, + { path: 'https://facebook.com/nibmcs', icon: }, + { path: 'https://github.com/nibmcs', icon: }, + { path: 'https://linkedin.com/company/nibmcs', icon: }, + { path: 'https://twitter.com/nibmcs', icon: }, +]; + +const Social = () => { + return ( +
+
+ {socialData.map((link, index) => { + return ( + +
+ {link.icon} +
+ + ); + })} +
+
+ ); +}; + +export default Social; diff --git a/src/components/theme-switch.tsx b/src/components/theme-switch.tsx new file mode 100644 index 0000000..e4089bc --- /dev/null +++ b/src/components/theme-switch.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { FC } from "react"; +import { VisuallyHidden } from "@react-aria/visually-hidden"; +import { SwitchProps, useSwitch } from "@nextui-org/switch"; +import { useTheme } from "next-themes"; +import { useIsSSR } from "@react-aria/ssr"; +import clsx from "clsx"; + +import { DarkModeIcon, LightModeIcon } from "@/components/icons"; + +export interface ThemeSwitchProps { + className?: string; + classNames?: SwitchProps["classNames"]; +} + +export const ThemeSwitch: FC = ({ + className, + classNames, +}) => { + const { theme, setTheme } = useTheme(); + const isSSR = useIsSSR(); + + const onChange = () => { + theme === "light" ? setTheme("dark") : setTheme("light"); + }; + + const { + Component, + slots, + isSelected, + getBaseProps, + getInputProps, + getWrapperProps, + } = useSwitch({ + isSelected: theme === "light" || isSSR, + "aria-label": `Switch to ${ + theme === "light" || isSSR ? "dark" : "light" + } mode`, + onChange, + }); + + return ( + + + + +
+ {!isSelected || isSSR ? : } +
+
+ ); +}; diff --git a/src/config/fonts.ts b/src/config/fonts.ts new file mode 100644 index 0000000..43a02f4 --- /dev/null +++ b/src/config/fonts.ts @@ -0,0 +1,8 @@ +import { Poppins } from 'next/font/google'; + +export const fontSans = Poppins({ + subsets: ['latin'], + weight: ['400', '500', '600', '700', '800'], + variable: '--font-sans', + style: 'normal', +}); diff --git a/src/config/site.ts b/src/config/site.ts new file mode 100644 index 0000000..c09a802 --- /dev/null +++ b/src/config/site.ts @@ -0,0 +1,41 @@ +export type SiteConfig = typeof siteConfig; + +export const siteConfig = { + name: 'NIBM Computing Society', + description: + 'We are a student community organization for the NIBM - Sri Lanka, established with the aim of enabling us to learn, share, and build our professions.', + navItems: [ + { + label: 'Events', + href: '/events', + active: false, + }, + { + label: 'Leaderboard', + href: '/leaderboard', + active: false, + }, + { + label: 'Blog', + href: '/blog', + active: false, + }, + { + label: 'About', + href: '/about', + active: false, + }, + { + label: 'Contact', + href: '/contact', + active: false, + }, + ], + // links: { + // facebook: 'https://facebook.com/nibmcs', + // instagram: 'https://instagram.com/nibmcs', + // twitter: 'https://twitter.com/nibmcs', + // linkedin: 'https://linkedin.com/company/nibmcs', + // github: 'https://github.com/nibmcs', + // }, +}; diff --git a/src/utils/variants.ts b/src/utils/variants.ts new file mode 100644 index 0000000..d1c8af1 --- /dev/null +++ b/src/utils/variants.ts @@ -0,0 +1,26 @@ +export const fadeIn = (direction: any, delay: any) => { + return { + hidden: { + y: direction === 'up' ? 80 : direction === 'down' ? -80 : 0, + opacity: 0, + x: direction === 'left' ? 80 : direction === 'right' ? -80 : 0, + transition: { + type: 'tween', + duration: 1.5, + delay: delay, + ease: [0.25, 0.6, 0.3, 0.8], + }, + }, + show: { + y: 0, + x: 0, + opacity: 1, + transition: { + type: 'tween', + duration: 1.4, + delay: delay, + ease: [0.25, 0.25, 0.25, 0.75], + }, + }, + }; +};