diff --git a/public/arrow-up-right.svg b/public/arrow-up-right.svg
new file mode 100644
index 0000000..e2034c2
--- /dev/null
+++ b/public/arrow-up-right.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/src/app/(frontend)/components/FAQItem.tsx b/src/app/(frontend)/components/FAQItem.tsx
index 61e0a44..bb872b4 100644
--- a/src/app/(frontend)/components/FAQItem.tsx
+++ b/src/app/(frontend)/components/FAQItem.tsx
@@ -9,6 +9,7 @@ const FAQItem = ({ question, answer }: FAQItemProps) => {
{question}
+
- {answer}
+ {answer}
)
}
diff --git a/src/app/(frontend)/components/FAQSection.tsx b/src/app/(frontend)/components/FAQSection.tsx
index b82e9e9..a26c981 100644
--- a/src/app/(frontend)/components/FAQSection.tsx
+++ b/src/app/(frontend)/components/FAQSection.tsx
@@ -1,6 +1,5 @@
import FAQItem from './FAQItem'
-// Sample FAQ data to be replaced with actual FAQs
const faqData = [
{
id: 1,
@@ -52,11 +51,13 @@ const FAQSection = () => {
FAQs
+
{faqData.map((item) => (
))}
+
)
diff --git a/src/app/(frontend)/components/OpportunitySection.tsx b/src/app/(frontend)/components/OpportunitySection.tsx
new file mode 100644
index 0000000..73d06d9
--- /dev/null
+++ b/src/app/(frontend)/components/OpportunitySection.tsx
@@ -0,0 +1,244 @@
+'use client'
+
+import { useEffect, useMemo, useState } from 'react'
+import OpportunityTable from './OpportunityTable'
+
+// TODO: replace with real data from API
+
+const opportunities = [
+ {
+ id: 1,
+ type: 'Scholarship',
+ title: 'Lodge of the Liberal Arts: Howard Wyatt Memorial Scholarship',
+ deadlineLabel: '20th of May, 11:59pm NZST',
+ deadlineDate: '2026-05-20T23:59:00+12:00',
+ description:
+ 'The Freemasons of Lodge No.500 have established a trust for charitable purposes, to assist young musicians in their education. Scholarships totalling $3,000 are granted each year to members of AYO who have shown outstanding...',
+ applyUrl: '#',
+ },
+ {
+ id: 2,
+ type: 'Scholarship',
+ title: 'Chip and Muriel Stevens Award',
+ deadlineLabel: '20th of May, 11:59pm NZST',
+ deadlineDate: '2026-05-20T23:59:00+12:00',
+ description:
+ 'This $1,500 award is dedicated to the memory of a former Chairman of AYO, N.W. (Chip) Stevens, who spent his lifetime encouraging young people to love music and young musicians to reach their full potential.',
+ applyUrl: '#',
+ },
+ {
+ id: 3,
+ type: 'Competition',
+ title: 'AYO Soloist Competition',
+ deadlineLabel: '15th of August, 11:59pm NZST',
+ deadlineDate: '2026-08-15T23:59:00+12:00',
+ description:
+ 'The AYO Soloist Competition offers existing orchestra members the chance to compete for monetary prizes and a concerto appearance with the orchestra. The orchestra showcases young soloists and composers; it...',
+ applyUrl: '#',
+ },
+ {
+ id: 4,
+ type: 'Scholarship',
+ title: 'AYO International Performance Grant',
+ deadlineLabel: '1st of June, 11:59pm NZST',
+ deadlineDate: '2026-06-01T23:59:00+12:00',
+ description:
+ 'Supports orchestra members travelling internationally for advanced musical training and performance opportunities.',
+ applyUrl: '#',
+ },
+ {
+ id: 5,
+ type: 'Competition',
+ title: 'Emerging Composer Competition',
+ deadlineLabel: '10th of July, 11:59pm NZST',
+ deadlineDate: '2026-07-10T23:59:00+12:00',
+ description:
+ 'Young composers are invited to submit original orchestral works for adjudication and potential live performance.',
+ applyUrl: '#',
+ },
+ {
+ id: 6,
+ type: 'Workshop',
+ title: 'Conducting Masterclass Programme',
+ deadlineLabel: '5th of June, 11:59pm NZST',
+ deadlineDate: '2026-06-05T23:59:00+12:00',
+ description:
+ 'A practical workshop series led by professional conductors focusing on rehearsal technique, score preparation, and ensemble leadership.',
+ applyUrl: '#',
+ },
+ {
+ id: 7,
+ type: 'Scholarship',
+ title: 'Regional Music Development Scholarship',
+ deadlineLabel: '25th of May, 11:59pm NZST',
+ deadlineDate: '2026-05-25T23:59:00+12:00',
+ description:
+ 'Financial assistance for students from regional communities pursuing advanced orchestral studies.',
+ applyUrl: '#',
+ },
+ {
+ id: 8,
+ type: 'Competition',
+ title: 'Chamber Ensemble Showcase',
+ deadlineLabel: '30th of September, 11:59pm NZST',
+ deadlineDate: '2026-09-30T23:59:00+12:00',
+ description:
+ 'Small ensembles compete for performance opportunities during the annual AYO concert season.',
+ applyUrl: '#',
+ },
+ {
+ id: 9,
+ type: 'Residency',
+ title: 'Composer-in-Residence Programme',
+ deadlineLabel: '18th of August, 11:59pm NZST',
+ deadlineDate: '2026-08-18T23:59:00+12:00',
+ description:
+ 'Selected applicants will collaborate directly with the orchestra over a six-month residency period developing new compositions.',
+ applyUrl: '#',
+ },
+ {
+ id: 10,
+ type: 'Workshop',
+ title: 'Advanced Audition Preparation Intensive',
+ deadlineLabel: '12th of June, 11:59pm NZST',
+ deadlineDate: '2026-06-12T23:59:00+12:00',
+ description:
+ 'An intensive coaching programme helping musicians prepare orchestral excerpts, solo repertoire, and audition strategies.',
+ applyUrl: '#',
+ },
+]
+
+export default function OpportunitySection() {
+ const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc')
+ const [selectedType, setSelectedType] = useState('All')
+ const [showCount, setShowCount] = useState(5)
+ const [currentPage, setCurrentPage] = useState(1)
+
+ // Dynamically generate available types
+ const opportunityTypes = ['All', ...new Set(opportunities.map((opp) => opp.type))]
+
+ // Filter opportunities
+ const filteredOpportunities =
+ selectedType === 'All'
+ ? opportunities
+ : opportunities.filter((opp) => opp.type === selectedType)
+
+ // Sort opportunities
+ const sortedOpportunities = useMemo(() => {
+ return [...filteredOpportunities].sort((a, b) => {
+ return sortOrder === 'asc'
+ ? new Date(a.deadlineDate).getTime() - new Date(b.deadlineDate).getTime()
+ : new Date(b.deadlineDate).getTime() - new Date(a.deadlineDate).getTime()
+ })
+ }, [filteredOpportunities, sortOrder])
+
+ // Reset page when controls change
+ useEffect(() => {
+ setCurrentPage(1)
+ }, [selectedType, sortOrder, showCount])
+
+ // Pagination
+ const totalPages = Math.ceil(sortedOpportunities.length / showCount)
+
+ const paginatedOpportunities = sortedOpportunities.slice(
+ (currentPage - 1) * showCount,
+ currentPage * showCount,
+ )
+
+ return (
+
+
+
Opportunities
+
+
+ There are a range of opportunities we offer, exclusively to AYO players.
+
+
+ {/* Controls */}
+
+ {/* Type */}
+
+
+
+
+
+
+ {/* Sort */}
+
+
+
+
+
+
+ {/* Show */}
+
+
+
+
+
+
+ {/* Count */}
+
+ Showing {paginatedOpportunities.length}{' '}
+ {paginatedOpportunities.length === 1 ? 'opportunity' : 'opportunities'}
+
+
+
+
+
+ {/* Table */}
+
+
+ {/* Pagination */}
+
+
+
+
+
+
+
+
+ {currentPage} of {totalPages}
+
+
+
+
+ )
+}
diff --git a/src/app/(frontend)/components/OpportunityTable.tsx b/src/app/(frontend)/components/OpportunityTable.tsx
new file mode 100644
index 0000000..6821b7f
--- /dev/null
+++ b/src/app/(frontend)/components/OpportunityTable.tsx
@@ -0,0 +1,85 @@
+'use client'
+
+import { useState } from 'react'
+import OpportunityModal from './OpportunityModal'
+import ArrowUpRight from '/arrow-up-right.svg'
+
+type Opportunity = {
+ id: number
+ type: string
+ title: string
+ deadlineLabel: string
+ deadlineDate: string
+ description: string
+ applyUrl: string
+}
+
+type OpportunityTableProps = {
+ opportunities: Opportunity[]
+}
+
+type OpportunityRowProps = Opportunity & {
+ onReadMore: () => void
+}
+
+const OpportunityRow = ({
+ title,
+ deadlineLabel,
+ description,
+ applyUrl,
+ onReadMore,
+}: OpportunityRowProps) => {
+ return (
+
+
+
{title}
+
+
Apply by {deadlineLabel}
+
+
+
{description}
+
+
+
+ )
+}
+
+const OpportunityTable = ({ opportunities }: OpportunityTableProps) => {
+ const [selectedOpp, setSelectedOpp] = useState(null)
+
+ return (
+
+ {opportunities.map((opp, index) => (
+
+ {index > 0 &&
}
+
+ setSelectedOpp(opp)} />
+
+ ))}
+
+ {/* Modal */}
+ {selectedOpp && (
+
setSelectedOpp(null)}
+ />
+ )}
+
+ )
+}
+
+export default OpportunityTable
diff --git a/src/app/(frontend)/join-ayo/page.tsx b/src/app/(frontend)/join-ayo/page.tsx
index 70e9e10..2a79da4 100644
--- a/src/app/(frontend)/join-ayo/page.tsx
+++ b/src/app/(frontend)/join-ayo/page.tsx
@@ -1,9 +1,11 @@
import FAQSection from '../components/FAQSection'
+import OpportunitySection from '../components/OpportunitySection'
+import OpportunityModal from '../components/OpportunityModal'
export default function JoinAyoPage() {
return (
- {/* This is the join AYO page
*/}
+
)